Damian Nadales
Damian Nadales

Reputation: 5037

Solving this ambiguous type issue without resorting to Proxy

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

Answers (2)

dfeuer
dfeuer

Reputation: 48591

When you really need to pass a type as an argument, you basically have four options, two standard and two GHC-specific:

Standard options

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]

GHC-specific options

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

Li-yao Xia
Li-yao Xia

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

Related Questions