Reputation: 5037
I have the following type-class:
class MapsTo k v m where
innerMap :: m -> Map k v
If I declare the following function signature:
keys :: MapsTo k v m => m -> [k]
I get the following error:
Could not deduce (MapsTo k v0 m)
from the context: MapsTo k v m
bound by the type signature for:
keys :: forall k v m. MapsTo k v m => m -> [k]
Which makes sense since v
is not used anywhere as type parameter.
Since I think using AllowAmbiguousTypes
is not a good idea, I resorted to Data.Proxy
:
keys :: MapsTo k v m => Proxy v -> m -> [k]
This works, but I'm wondering if there's a way to solve this without resorting to Proxy
.
Note that for my problem at hand, any two combination of k
, v
, or m
does not uniquely determine the remaining type. So sadly, using functional dependencies won't help in my case.
Upvotes: 1
Views: 124
Reputation: 48591
When you really need to pass a type as an argument, you basically have four options, two standard and two GHC-specific:
As you've recognized, you can use proxy
arguments, typically instantiated to Proxy
:
keys :: MapsTo k v m => proxy v -> m -> [k]
The caller may (but need not) call this with a Proxy
value; if they have another type on hand with the right structure, that will do as well.
The other standard option is to use either Const
or its flipped cousin Tagged
, which was designed for this purpose:
import Data.Tagged
-- Either
keys :: MapsTo k v m => Tagged v (m -> [k])
-- or
keys :: MapsTo k v m => m -> Tagged v [k]
You're unlikely to want it, but GHC.Exts
offers a type Proxy#
, which is a sort of proxy that's never actually passed at runtime, so it shouldn't have any performance cost.
keys :: MapsTo k v m => Proxy# v -> m -> [k]
-- called like this:
keys (proxy# :: Proxy# Int) m
Whereas it was once mostly useless, AllowAmbiguousTypes
is now valuable in combination with TypeApplications
and ScopedTypeVariables
. You can write
keys :: forall v m k. MapsTo k v m => m -> [k]
:t keys @Int
keys @Int :: MapsTo k Int m => m -> [k]
Upvotes: 2
Reputation: 33399
The dependency between k
, v
, m
might be described functionally by an additional parameter. For example, let's call it f
for "field", and assume that f
and k
determine v
:
class MapsTo f k v m | f k -> v where
innerMap :: m -> Map k v
keys :: forall f k v m. MapsTo f k v m => m -> [k]
Then you can apply keys
to f
instead of v
, which can sometimes be more elegant, e.g., when f
is just a symbol and v
is a complex type that you'd rather not spell out.
keys @f :: m -> [k] -- v determined by f and k
Upvotes: 2