所以我试着问一般的“how do you debug type-level programming problems?”问题,看起来要么问题太笼统,也不可能调试这些问题。 : - }
在这个特殊的日子里,让我烦恼的问题是一个中等大小的复杂程序。我已经设法把它归结为一个仍然出错的相当小的核心。我们走了:
{-#
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
然后它完美编译(顺便说一句,我得到正确的值输出,这很好)。那我在哪里错过了约束?
答案 0 :(得分:3)
编译器还没有发现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's MonadReader
class有它的fundep:出于类型推断的原因,人们经常希望monad能够唯一地确定你可以get
从中获得什么样的东西。