7

I have an enumeration type, e.g.

data MyType = A | B

And I want to be able to pass values of this type implicitly to my functions. I can do this using the ImplicitParams GHC extension like this:

type HasMyType = (?myType :: MyType)

myFun :: HasMyType => String
myFun = case ?myType of
    A -> "Foo"
    B -> "Bar"

But I've heard many times that it's better to use the Haskell package reflection for this task. Unfortunately, the reflection documentation doesn't explain how to write a similar code using the library. And it's not that straightforward to figure it out.

So, my question is, whether it's possible to use the reflection library to implement a similar code and to satisfy the following requirements?

  1. Values of MyType should be passed implicitly.
  2. If the HasMyType constraint is not specified, the default value of MyType should be taken.
  3. It should be possible to override the value passed via the HasMyType constraint in a single place, e.g. at the beginning of the application.

Does something like this is possible? Or what would be the closest approximation of this using the reflection library?

Shersh
  • 9,019
  • 3
  • 33
  • 61

1 Answers1

3

This answers two ways of implementing question 1. using reflection.

Using Reifies:

type HasMyType :: forall k. k -> Constraint
type HasMyType name = Reifies name MyType

myFun :: HasMyType name => Proxy name -> String
myFun name = case reflect name of
  A -> "Foo"
  B -> "Bar"

-- reify :: MyType -> (forall name. HasMyType name => Proxy name -> res) -> res
>> reify A myFun
"Foo"
>> reify B myFun
"Bar"
>> reify A \name -> myFun name
"Foo"
>> reify B \name -> myFun name
"Bar"

Haskell can't abstract over type variables yet \@name -> .. so it uses \(Proxy :: Proxy name) -> ...

The Proxy can be removed from myFun where name is supplied with visible type applications, but reify still generates a Proxy whose name must be "extracted"

{-# Language ScopedTypeVariables #-}
{-# Language TypeApplications #-} ..

myFun :: forall name. HasMyType name => String
myFun = case reflect @name Proxy of
  A -> "Foo"
  B -> "Bar"

>> reify A \(_ :: _ name) -> myFun @name
"Foo"
>> reify B \(_ :: _ name) -> myFun @name
"Bar"

A simpler option (Given) doesn't rely on type-level "names" to distinguish between different dictionaries, therefore it is more dangerous with the following warning:

You should only give a single value for each type. If multiple instances are in scope, then the behavior is implementation defined.

type HasMyType :: Constraint
type HasMyType = Given MyType

myFun :: HasMyType => String
myFun = case given of
  A -> "Foo"
  B -> "Bar"

-- give :: MyType -> (HasMyType => res) -> res
>> give A myFun
"Foo"
>> give B myFun
"Bar"
Iceland_jack
  • 6,848
  • 7
  • 37
  • 46
  • 1
    I would go a bit further. `Given` is really only appropriate for singletons or similar: if the type has more than one non-bottom inhabitant, and you might care which one you get, then you shouldn't use `Given`. – dfeuer Sep 02 '20 at 13:39
  • This answer doesn't explain how `reify` is supposed to pass non-singleton values _implicitly_. Proxies or type applications are still explicit. – leftaroundabout Sep 02 '20 at 14:20
  • Thanks for the answer! But yeah, looks like I can't use `Given` because I have two values of the type. And `Reifies` is not implicit, I need to either pass `Proxy` or use `TypeApplications` to pass it implicitly. `ImplicitParams` almost solve my problem, but the solution with `ImplicitParams` doesn't work due to a bug in GHC. – Shersh Sep 02 '20 at 14:38
  • @Shersh, the usual way to get good ergonomics with `reflection` is to tie the `s` type in with a phantom somewhere. There's no way to get the default-unless-overridden behavior you seek, as far as I know. – dfeuer Sep 02 '20 at 17:49
  • @dfeuer Thanks for your insights! I was hoping that ImplicitParams with the "Global implicit params" technique is able to get the "default-unless-overridden" behaviour, but the approach doesn't work with the optimizations enabled: https://gitlab.haskell.org/ghc/ghc/-/issues/18627 – Shersh Sep 02 '20 at 19:29
  • See Edward Kmett's comment: https://www.reddit.com/r/haskell/comments/85nnti/why_not_make_type_class_instances_firstclass/dvzra7i/ – Iceland_jack Sep 11 '20 at 19:41