我有一个函数,其职责是计算a
类型的某个最佳值,以及a -> v
类型的某个值函数
type OptiF a v = (a -> v) -> a
然后我有一个容器想要将这样的函数与另一个使用值值的函数一起存储:
data Container a = forall v. (Ord v) => Cons (OptiF a v) (a -> Int)
这个想法是,实现OptiF a v
类型函数的人不应该被v
的细节所困扰,除非它是Ord
的实例。
所以我编写了一个带有这样的值函数和容器的函数。使用OptiF a v
它应该计算最优值wrt val
并将其插入容器的result
函数中:
optimize :: (forall v. (Ord v) => a -> v) -> Container a -> Int
optimize val (Cons opti result) = result (opti val)
到目前为止一直很好,但我无法拨打optimize
,因为
callOptimize :: Int
callOptimize = optimize val cont
where val = (*3)
opti val' = if val' 1 > val' 0 then 100 else -100
cont = Cons opti (*2)
无法编译:
Could not deduce (v ~ Int)
from the context (Ord v)
bound by a type expected by the context: Ord v => Int -> v
at bla.hs:12:16-32
`v' is a rigid type variable bound by
a type expected by the context: Ord v => Int -> v at bla.hs:12:16
Expected type: Int
Actual type: Int
Expected type: Int -> v
Actual type: Int -> Int
In the first argument of `optimize', namely `val'
In the expression: optimize val cont
第12:16-32行为optimize val cont
。
在这种情况下,我是否误解了存在类型? forall v
声明中的optimize
是否意味着optimize
可能期望a -> v
它想要的v
是什么?或者这是否意味着optimize
除了a -> v
之外Ord v
可能没有任何意义?
我想要的是OptiF a v
并未针对任何v
修复,因为我想稍后插入一些a -> v
。我想要强加的唯一约束是Ord v
。是否有可能使用存在类型(或其他)来表达类似的内容?
我设法通过一个额外的类型类来实现这一点,该类型为optimize
函数提供了与OptiF a v
类似的签名,但这对我来说比使用更高阶函数更加丑陋。
答案 0 :(得分:12)
这很容易出错。
optimize
签名中的内容不是存在主义,而是普遍存在。
...因为existentials有点过时了,让我们将你的数据重写为GADT形式,这使得这一点更加清晰,因为语法基本上与多态函数相同:
data Container a where
(:/->) :: Ord v => -- come on, you can't call this `Cons`!
OptiF a v -> (a->Int) -> Container a
观察Ord
约束(这意味着这里是forall v...
)位于类型变量参数化函数签名之外,即 v
是我们可以参数当我们想要构建Container
值时,从外部指示。换句话说,
对于
v
中的所有Ord
,存在构造函数(:/->) :: OptiF a v -> (a->Int) -> Container a
这就是“存在型”的名称。同样,这类似于普通的多态函数。
另一方面,在签名
中optimize :: (forall v. (Ord v) => a -> v) -> Container a -> Int
您在签名字词本身中有一个forall
,这意味着{em>被调用者,v
可以确定具体类型optimize
。 ,在内部 - 我们从外部控制的是它在Ord
。没有什么“存在主义”,这就是为什么这个签名实际上不会仅用XExistentialQuantification
或XGADTs
进行编译的原因:
<interactive>:37:26:
Illegal symbol '.' in type
Perhaps you intended -XRankNTypes or similar flag
to enable explicit-forall syntax: forall <tvs>. <type>
val = (*3)
显然无法满足(forall v. (Ord v) => a -> v)
,它实际上需要Num
个实例,并非所有Ord
都有。实际上,optimize
不应该使用rank2类型:它应该适用于调用者可能提供给它的任何Ord
- 类型v
。
optimize :: Ord v => (a -> v) -> Container a -> Int
在这种情况下,您的实现不再起作用了:因为(:/->)
实际上是一个存在主义的构造函数,它只需要包含任何 OptiF
函数,因为< em> some 未知类型v1
。因此,优化调用者可以自由选择任何特定类型的opti函数,并且可以针对任何可能的其他固定类型进行优化 - 这是无法工作的!
您想要的解决方案是: Container
不应该是存在的,! opti-function应该适用于Ord
中的任何类型,而不仅仅适用于某种特定类型。好吧,作为一个GADT,这与您最初为optimize
所拥有的普遍量化签名大致相同:
data Container a where
(:/->) :: (forall v. Ord v => OptiF a v) -> (a->Int) -> Container a
现在,优化工作
optimize :: Ord v => (a -> v) -> Container a -> Int
optimize val (opti :/-> result) = result (opti val)
可以按需使用
callOptimize :: Int
callOptimize = optimize val cont
where val = (*3)
opti val' = if val' 1 > val' 0 then 100 else -100
cont = opti :/-> (*2)