在不诉诸代理的情况下解决这种模糊的类型问题

时间:2018-04-13 13:27:12

标签: haskell

我有以下类型类:

class MapsTo k v m where
    innerMap :: m -> Map k v

如果我声明以下函数签名:

keys :: MapsTo k v m => m -> [k]

我收到以下错误:

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]

这是有道理的,因为v并未在任何地方用作类型参数。

由于我认为使用AllowAmbiguousTypes并不是一个好主意,所以我采用了Data.Proxy

keys :: MapsTo k v m => Proxy v -> m -> [k]

这有效,但我想知道是否有办法解决这个问题而不诉诸Proxy

请注意,对于我手头的问题,kvm的任意两种组合并不能唯一地确定剩余类型。很遗憾,在我的案例中使用函数依赖将无济于事。

2 个答案:

答案 0 :(得分:2)

当你真的需要传递一个类型作为参数时,你基本上有四个选项,两个标准选项和两个GHC特定选项:

标准选项

如您所知,您可以使用proxy个参数,通常会实例化为Proxy

keys :: MapsTo k v m => proxy v -> m -> [k]

调用者可以(但不一定)用Proxy值调用它;如果他们手头有另一种类型的正确结构,那也可以。

另一个标准选项是使用专为此目的设计的Const或其翻转的表兄Tagged

import Data.Tagged

-- Either
keys :: MapsTo k v m => Tagged v (m -> [k])
-- or
keys :: MapsTo k v m => m -> Tagged v [k]

GHC特定选项

你不太可能想要它,但是GHC.Exts提供了一种类型Proxy#,这是一种在运行时从未真正传递过的代理,所以它不应该&#39 ; t有任何性能成本。

keys :: MapsTo k v m => Proxy# v -> m -> [k]
-- called like this:
keys (proxy# :: Proxy# Int) m

虽然它曾经几乎无用,但AllowAmbiguousTypes现在与TypeApplicationsScopedTypeVariables结合使用很有价值。你可以写

keys :: forall v m k. MapsTo k v m => m -> [k]

:t keys @Int
keys @Int :: MapsTo k Int m => m -> [k]

答案 1 :(得分:2)

kvm之间的依赖关系可能会通过其他参数在功能上进行描述。例如,我们将其称为f为“字段”,并假设fk确定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]

然后,您可以将keys应用于f而不是v,这有时会更优雅,例如,f只是一个符号而v是一种复杂的类型,你不想拼出来。

keys @f :: m -> [k]  -- v determined by f and k