类型签名太笼统;缺少约束?

时间:2016-05-09 17:22:09

标签: haskell types

所以我试着问一般的“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

换句话说,对于任何irunReader会在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

因此,getActM 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

然后它完美编译(顺便说一句,我得到正确的值输出,这很好)。那我在哪里错过了约束?

1 个答案:

答案 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从中获得什么样的东西。