考虑以下SML功能:
fn x => x x
这会产生以下错误(新泽西标准ML v110.72):
stdIn:1.9-1.12 Error: operator is not a function [circularity]
operator: 'Z
in expression:
x x
我可以理解为什么不允许这样做 - 一方面,我不确定如何写下它的类型 - 但它并非完全没有意义;例如,我可以将身份功能传递给它并将其取回。
这个功能有名字吗? (有没有办法在SML中表达它?)
答案 0 :(得分:7)
无法用具有ML类型系统的语言表达此功能。即使使用身份功能,它也行不通,因为x
中的第一个x x
和第二(_a -> _a) -> (_a -> _a)
必须是该功能的不同实例,类型为_a -> _a
和{{1} },分别用于某种类型_a
。
事实上,类型系统被设计为禁止像
这样的结构(λx . x x) (λx . x x)
在无类型的lambda演算中。在动态类型语言Scheme中,您可以编写此函数:
(define (apply-to-self x) (x x))
并获得预期结果
> (define (id x) x)
> (eq? (apply-to-self id) id)
#t
答案 1 :(得分:4)
这样的函数经常在定点组合器中遇到。例如Y combinator的一种形式是λf.(λx.f (x x)) (λx.f (x x))
。定点组合器用于在无类型的lambda演算中实现一般递归,而不需要任何额外的递归构造,这是使无类型lambda演算Turing-complete的部分原因。
当人们开发simply-typed lambda calculus,这是一个基于lambda演算的天真静态类型系统时,他们发现不再可能编写这样的函数。实际上,在简单类型的lambda演算中不可能执行一般递归;因此,简单类型的lambda演算不再是图灵完备的。 (一个有趣的副作用是简单类型的lambda演算中的程序总是终止。)
标准ML等真正的静态类型编程语言需要内置的递归机制来克服这个问题,例如命名递归函数(用val rec
或fun
定义)和命名递归数据类型。
仍然可以使用递归数据类型来模拟你想要的东西,但它不是那么漂亮。
基本上,您想要定义类似'a foo = 'a foo -> 'a
的类型;但是,这是不允许的。您可以将其包装在数据类型中:datatype 'a foo = Wrap of ('a foo -> 'a);
datatype 'a foo = Wrap of ('a foo -> 'a);
fun unwrap (Wrap x) = x;
基本上,Wrap
和unwrap
用于在'a foo
和'a foo -> 'a
之间进行转换,反之亦然。
每当你需要自己调用一个函数而不是x x
时,你必须明确地写(unwrap x) x
(或unwrap x x
);即unwrap
将其转换为可以应用于原始值的函数。
P.S。另一种ML语言OCaml可以选择启用递归类型(通常是禁用的);如果您使用-rectypes
标志运行解释器或编译器,则可以编写fun x -> x x
之类的内容。基本上,在幕后,类型检查器会找出需要“包装”和“解包”递归类型的位置,然后为您插入它们。我不知道任何具有类似递归类型功能的标准ML实现。