类型签名过于笼统; 缺少约束?
所以我试着问一个将军:“你怎么调试类型编程问题?” 问题,似乎或者这个问题太笼统,无法回答,或者也许不可能调试这样的事情。 : - }
在这个特定的日子,令我感到厌烦的是一个中等大小,复杂的程序。 我已经设法将它归结为一个仍然出错的合理小核心。 开始了:
{-#
LANGUAGE
GADTs, MultiParamTypeClasses,
FlexibleInstances, FlexibleContexts, RankNTypes,
KindSignatures
#-}
module Minimal where
-------------------------------------------------------------------------------
data Union (t :: * -> *) (ts :: * -> *) x
class Member (x :: * -> *) (ys :: * -> *) where
instance Member x (Union x ys) where
instance (Member x ys) => Member x (Union y ys) where
-------------------------------------------------------------------------------
data ActM (effects :: * -> *) x
instance Monad (ActM effects) where
-------------------------------------------------------------------------------
data Reader i x where
Get :: Reader i i
get :: Member (Reader i) req => ActM req i
get = undefined
runReader :: i -> ActM (Union (Reader i) req) x -> ActM req x
runReader = undefined
(另外:我查看了这个语言扩展名列表,并认为“也许这只是一个可怕的主意!”)
无论如何,真正有趣的部分是在底部。 我们看到了
runReader :: i -> ActM (Union (Reader i) req) x -> ActM req x
换句话说,对于任何i
, runReader
在ActM
monad中执行一个操作,将Reader i
作为其可能影响之一,并在ActM
monad中返回一个操作,但不会产生可能的影响。 很简单,对吧?
现在,如果我传递(比如说)一个Char
作为第一个参数,那么阅读器类型被固定为Char
:
runReader 'X' :: ActM (Union (Reader Char) req) x -> ActM req x
到现在为止还挺好。 如果我只是返回一些数据,一切都很好:
runReader 'X' (return True) :: ActM req Bool
(令人欣慰的是,我也得到了正确的价值!)
但是现在, get
函数呢?
get :: Member (Reader i) req => ActM req i
所以get
是ActM
monad中针对包含Reader i
任何一组效果的操作。 这意味着如果我将它传递给runReader
那么我就会得到...
runReader 'X' get :: Member (Reader x) (Union (Reader Char) req) => ActM req x
...等等,什么?!? 为什么编译器不知道x
必须是Char
?
事实上,如果我添加一个明确的类型签名说
runReader 'X' get :: ActM req Char
那么它编译完美(顺便说一下,我得到正确的值输出,这很好)。 那么我在哪里错过了一个约束?
编译器没有发现x
必须是Char
因为它不是真的。 例如,有人可能会写
type ReadsBool req = Union (Reader Bool) req
type ReadsCharAndBool req = Union (Reader Char) (ReadsBool req)
然后你有:
runReader 'X' (get :: ReadsCharAndBool req) :: ActM (ReadsBool req) Bool
所以,恭喜,现在你知道为什么mtl的MonadReader
类具有它的fundep:为了类型推断的原因,人们通常希望monad能够唯一确定你可以get
什么样的东西。