外部类型签名的需求是由单态化限制引起的。
这一点的线索在于你定义的左侧。
dummyTest = ...
由于此定义没有任何参数,编译器将尝试使定义单态化。添加类型签名会覆盖此行为。
然而,正如您所指出的那样,这还不够。由于某种原因,编译器无法推断内部表达式的类型。为什么呢?让我们来玩一下类型推断引擎!
让我们从外部类型开始,尝试计算内部表达式的类型。
dummyTest :: forall m. Monad m => m ()
dummyTest = def "dummy" (return ())
def
的类型是
FcnDef β m => String -> β
,但是我们将
def
应用到了两个参数上。这告诉我们,
β
必须是一个函数类型。让我们称之为
x -> y
。
然后,我们可以很容易地推断出
y
必须等于
m()
,以满足外部类型。此外,
return ()
参数的类型是
Monad m1 => m1 ()
,因此我们可以推断出我们要找的
def
类型必须具有类型
FcnDef (m1() -> m()) m0 => def :: String -> m1() -> m()
。
接下来,我们将继续查找要使用的实例。唯一可用的实例
不够通用,因为它要求
m1
和
m
相同。因此,我们会发出像这样的错误消息:
Could not deduce (FcnDef (m1 () -> m ()) m0)
arising from a use of `def'
from the context (Monad m)
bound by the type signature for dummyTest :: Monad m => m ()
at FcnDef.hs:10:1-51
Possible fix:
add (FcnDef (m1 () -> m ()) m0) to the context of
the type signature for dummyTest :: Monad m => m ()
or add an instance declaration for (FcnDef (m1 () -> m ()) m0)
In the expression: def "dummy" ((return ()))
In an equation for `dummyTest':
dummyTest = def "dummy" ((return ()))
这里需要注意的关键是,某个特定实例恰好存在并不会影响我们在尝试推断类型时所做的选择。
因此,我们要么手动使用作用域类型变量来指定这个约束,要么可以将其编码到类型类中。
class Monad m => FcnDef β m | β -> m where
def :: String -> β -> β
instance Monad m => FcnDef (m α) m where
def s body = body
dummyTest = def "dummy" (return ())
现在类型推断引擎可以轻松确定
return
的单子必须与结果中的单子相同,并且使用
NoMonomorphismRestriction
,我们还可以删除外部类型签名。
当然,我不100%确定你想要达到什么目的,所以你需要判断这是否符合你尝试做的事情的上下文。