好的,我不知道怎么做,但是知道Haskell是一种无限深度的语言,我决定在放弃之前问这个。
假设我们有一个多态的值,例如:
foo :: Monad m => m Int
foo = return Int
显然,根据上下文,m
可以实例化为不同的类型。我想知道现在是否可以采用这段抽象的代码并在同一函数内的几个不同的上下文中使用它(例如在覆盖代码时减少样板):
bar :: Monad m => m Int -> Property
bar m = conjoin
[ checkIt (runIdentityT m)
, checkIt (runReaderT m ())
, …
]
checkIt
接受一些具体的monad。由于foo
基本上是抽象的描述如何做某事,应该可以在几个上下文中使用它,但问题显然是如果我们承诺使用{{1的签名中的任何Monad m => m Int
那么我们就不能在不太抽象的层次上编写这个函数,因为在它的实现中它不可能适应bar
的每个可能的实例。
有没有办法将Monad
传递给foo
,以便可以在其中的多个类型上下文中使用它?
答案 0 :(得分:10)
您想要排名2类型:
bar :: (forall m. Monad m => m Int) -> Property
bar m = conjoin
[ checkIt (runIdentityT m)
, checkIt (runReaderT m ())
, …
]
现在,bar foo
将输入check。
具体而言,类型(forall m. Monad m => m Int)
要求参数为多态,并且可用于所有 monads m
。相比之下,原始类型
bar :: Monad m => m Int -> Property
仅要求参数为某些 monad m Int
提供m
形式的类型。在你的情况下,你显然想要“为所有人”,而不是“为某些人”。
您可以通过在文件顶部添加以下行来启用rank-2类型:
{-# LANGUAGE Rank2Types #-}
从理论上讲,我的胆量告诉我类型
(forall m. Monad m => m Int)
实际上是同构的
Int
with isomorphisms:
iso :: Int -> (forall m. Monad m => m Int)
iso x = return x
osi :: (forall m. Monad m => m Int) -> Int
osi m = runIdentity m
由于与forall m. Monad m => m Int
相关的自由定理,上述确实应该是一个同构。
如果我的直觉是正确的,我认为,这意味着forall m. Monad m => m Int
类型的每个值都具有形式为return x
的某个整数x
}。
所以,最后的反问题是:你为什么不简单地传递Int
,并删除不需要的rank-2机器?