假设我有一个复合数据类型 -
data M o = M (String,o)
现在,我可以定义一个适用于所有M
的函数,而不管o
。例如 -
f :: M o -> M o
f (M (s,o)) = M (s++"!", o)
然而,f
并不像我希望的那样普遍。特别是,在表达式中使用f
会修复o
的类型,因此我无法再使用f
来使用不同类型的o
。例如,以下不是类型检查 -
p f = undefined where
m1 = M ("1", ())
m2 = M ("2", True)
m1' = f m1
m2' = f m2
它产生错误 - Couldn't match expected type 'Bool' with actual type '()'
令人惊讶的是,如果我不提供f作为参数而只是简单地使用f的全局定义那么它编译并且工作正常!即这个编译 -
p = undefined where
m1 = M ("1", ())
m2 = M ("2", True)
m1' = f m1
m2' = f m2
这有什么特别的原因吗?我如何解决这个问题,即定义一个可以应用于所有f
的函数(M o)
,即使o
在同一个表达式中变化?我猜测存在类型在这里发挥作用,但我无法弄清楚如何。
答案 0 :(得分:15)
问题是编译器无法正确推断p的类型。你必须给它一个类型签名:
p :: (forall o. M o -> M o) -> a
这是排名2类型,因此您需要语言扩展名:
{-# LANGUAGE Rank2Types #-}
或
{-# LANGUAGE RankNTypes #-}
答案 1 :(得分:5)
这有什么特别的原因吗?
确实,有一个。用一句话来说:如果取消HM系统对lambda参数必须是单态的限制,那么类型推断将无法正常工作。
Simon Peyton Jones设计了一种类型检查器,它扩展了HM并允许更高级别的多态性。但在这种情况下,需要显式类型注释。见Sjoerds答案。
答案 2 :(得分:2)
函数f
范围内的p f
与顶级函数f
不同。它是在调用p
时作为参数给出的任何函数。由于您未给出p
类型签名,因此编译器必须根据您使用它的方式推断f
。您的首次使用意味着f :: M () -> M ()
,即p :: (M () -> M ()) -> a
。正如其他人所说,你必须使用p
给forall
一个明确的签名来做你想做的事。