如何定义将多态函数应用于特定类型的高阶函数

时间:2016-11-25 15:45:58

标签: sml smlnj polyml

如果我定义

fun id x = x

然后自然id的类型为'a -> 'a

当然,id 0评估为0,这非常有意义。

由于这很有意义,我应该能够通过函数封装它:

fun applyToZero (f: 'a -> 'a) = f 0

希望applyToZero('a -> 'a) -> int类型和applyToZero id评估为0

但是当我尝试如上定义applyToZero时,SML / NJ会给出一条奇怪的错误消息:

unexpected exception (bug?) in SML/NJ: Match [nonexhaustive match failure]
  raised at: ../compiler/Elaborator/types/unify.sml:84.37

这几乎看起来像编译器本身的一个错误。很奇怪,但可能。

但是PolyML也不喜欢它(虽然它的错误信息不那么奇怪):

> fun applyToZero (f: 'a -> 'a) = f 0;
poly: : error: Type error in function application.
   Function: f : 'a -> 'a
   Argument: 0 : int
   Reason: Can't unify int to 'a (Cannot unify with explicit type variable)
Found near f 0

以下工作正常:

fun ignoreF (f: 'a -> 'a) = 1

使用推断类型('a -> 'a) -> int。这表明创建这种类型的高阶函数是不可能的。

为什么SML不接受我对applyToZero的定义?是否有任何解决方法可以让我定义它以使其类型为('a -> 'a) -> int

动机:在我试图解决this question中的谜题时,我能够定义类型tofun的函数int -> 'a -> 'a和另一个具有所需属性的函数fromfun对于所有整数fromfun (tofun n) = n n。但是,我的工作fromfun的推断类型是('int -> 'int) -> 'int)。我尝试添加类型注释以便SML将其作为('a -> 'a) -> int接受的所有尝试都失败了。我不想显示我对fromfun的定义,因为提出该问题的人可能仍在处理该问题,但applyToZero的定义会触发完全相同的错误消息。

2 个答案:

答案 0 :(得分:4)

它不能像普通的Hindley-Milner那样完成,就像SML一样,因为它不支持所谓的排名较高的或一流的多态性。类型注释'a -> 'a和类型('a -> 'a) -> int并不代表您的想法。

如果我们明确地为类型变量创建绑定器,那就更清楚了。

fun ignoreF (f: 'a -> 'a) = 1

实际上意味着

fun 'a ignoreF (f: 'a -> 'a) = 1

也就是说,'a是整个函数ignoreF的参数,而不是参数f。因此,函数的类型是

ignoreF : ∀ 'a. (('a -> 'a) -> int)

在这里,我将类型'a中的绑定器显式化为通用量词。这就是你如何在类型理论中编写这样的类型,而SML将所有量词隐含在其语法中。现在你认为这个类型会被写成

ignoreF : (∀ 'a. ('a -> 'a)) -> int

注意区别:在第一个版本中,ignoreF的调用者可以选择实例化'a的方式,因此它可以是任何东西,并且该函数不能假设其int(这就是applyToZero不进行类型检查的原因。在第二种类型中,参数的调用者可以选择,即ignoreF

但Hindley-Milner并不支持这种类型。它只支持所谓的 prenex多态性(或秩0多态),其中所有∀都在最外层 - 这就是为什么它可以保持隐含,因为在这个限制下没有歧义。排名较高的多态性的问题在于它的类型推断是不可判定的。

所以你的applyToZero不能拥有你想要的SML类型。实现类似功能的唯一方法是使用模块系统及其仿函数:

functor ApplyToZero (val f : 'a -> 'a) = struct val it = f 0 end

顺便说一句,您从SML / NJ引用的错误消息不可能由您显示的代码引起。你必须做其他事情。

答案 1 :(得分:1)

如果我们在fun applyToZero f = f 0上使用Hindley-Milner类型推理算法,由于术语f : int -> 'a,我们将获得f 0

显然,f是一个函数f : 'b -> 'a。我们将此函数应用于0,因此'b = int。因此,显式类型注释f : 'a -> 'a会产生您观察到的错误。

顺便说一句,SML / NJ v110.80在我的机器上正常工作并打印以下错误信息:

stdIn:2.39-2.42 Error: operator and operand don't agree [overload - user bound tyvar]
  operator domain: 'a
  operand:         [int ty]
  in expression:
    f 0