Haskell 中的“真正”通用函数

Posted

技术标签:

【中文标题】Haskell 中的“真正”通用函数【英文标题】:A "truly" generic function in Haskell 【发布时间】:2011-08-19 11:47:14 【问题描述】:

假设我有一个复合数据类型 -

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

这有什么特别的原因吗?我该如何解决这个问题,即定义一个可以应用于所有(M o) 的函数f,即使o 在同一个表达式中变化?我猜存在类型在这里发挥作用,但我就是不知道如何。

【问题讨论】:

为什么要让 M 成为一个元组?多么丑陋。 默认情况下有些东西是“forall”,有些东西不是。你可能会看到这些文字:en.wikibooks.org/wiki/Haskell/Existentially_quantified_types @monadic,这实际上只是一个最小的示例,它重现了我在实际代码中遇到的问题(这要复杂得多)。 【参考方案1】:

问题是编译器无法正确推断 p 的类型。你必须给它一个类型签名:

p :: (forall o. M o -> M o) -> a

这是 2 级类型,因此您需要语言扩展:

-# LANGUAGE Rank2Types #-

-# LANGUAGE RankNTypes #-

在此处了解更多信息:http://www.haskell.org/haskellwiki/Rank-N_types

【讨论】:

谢谢!添加该类型签名有效。我认为它与 forall 关键字有关,但不知道 RankN 类型。看来我要读书了!【参考方案2】:

这有什么特别的原因吗?

确实,有一个。一句话概括:如果解除 HM 系统对 lambda 参数必须是单态的限制,类型推断将无法正常工作。

Simon Peyton Jones 设计了一个类型检查器,它扩展了 HM 并允许更高等级的多态性。但在这种情况下,需要显式类型注释。请参阅 Sjoerds 答案。

【讨论】:

【参考方案3】:

函数p f范围内的f与***函数f不同。它是在调用p 时作为参数给出的任何函数。由于您没有给 p 类型签名,编译器必须从您使用它的方式推断出 f 是什么。您的第一次使用意味着f :: M () -> M (),即p :: (M () -> M ()) -> a。正如其他人所说,您必须使用forallp 一个明确的签名才能执行您想做的事情。

【讨论】:

以上是关于Haskell 中的“真正”通用函数的主要内容,如果未能解决你的问题,请参考以下文章

Haskell代码编程

将“为啥函数式编程很重要”翻译成 Haskell

Haskell 和函数组合

Haskell

Haskell拓展篇三:Fix和Fixpoint

从Haskell中的元组中提取第n个元素(其中n和元组被赋予参数)