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?
MyType
should be passed implicitly.HasMyType
constraint is not specified, the default value of MyType
should be taken.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?
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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With