我有以下类型类:
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
。
请注意,对于我手头的问题,k
,v
或m
的任意两种组合并不能唯一地确定剩余类型。很遗憾,在我的案例中使用函数依赖将无济于事。
答案 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.Exts
提供了一种类型Proxy#
,这是一种在运行时从未真正传递过的代理,所以它不应该&#39 ; t有任何性能成本。
keys :: MapsTo k v m => Proxy# v -> m -> [k]
-- called like this:
keys (proxy# :: Proxy# Int) m
虽然它曾经几乎无用,但AllowAmbiguousTypes
现在与TypeApplications
和ScopedTypeVariables
结合使用很有价值。你可以写
keys :: forall v m k. MapsTo k v m => m -> [k]
:t keys @Int
keys @Int :: MapsTo k Int m => m -> [k]
答案 1 :(得分:2)
k
,v
,m
之间的依赖关系可能会通过其他参数在功能上进行描述。例如,我们将其称为f
为“字段”,并假设f
和k
确定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