在几乎所有的例子中,ML类型语言中的y-combinator都是这样编写的:
let rec y f x = f (y f) x
let factorial = y (fun f -> function 0 -> 1 | n -> n * f(n - 1))
这可以按预期工作,但使用let rec ...
定义y-combinator感觉就像作弊一样。
我想使用标准定义来定义这个组合子而不使用递归:
Y = λf·(λx·f (x x)) (λx·f (x x))
直接翻译如下:
let y = fun f -> (fun x -> f (x x)) (fun x -> f (x x));;
然而,F#抱怨它无法弄清楚类型:
let y = fun f -> (fun x -> f (x x)) (fun x -> f (x x));;
--------------------------------^
C:\Users\Juliet\AppData\Local\Temp\stdin(6,33): error FS0001: Type mismatch. Expecting a
'a
but given a
'a -> 'b
The resulting type would be infinite when unifying ''a' and ''a -> 'b'
如何在不使用let rec ...
的情况下在F#中编写y-combinator?
答案 0 :(得分:51)
正如编译器指出的那样,没有可以分配给x
的类型,因此表达式(x x)
是良好类型的(这不是严格正确的;您可以显式键入{{ 1}}作为x
- 见我的最后一段)。您可以通过声明递归类型来解决此问题,以便可以使用非常相似的表达式:
obj->_
现在Y-combinator可以写成:
type 'a Rec = Rec of ('a Rec -> 'a)
不幸的是,你会发现这不是很有用,因为F#是一种严格的语言,
所以你尝试使用这个组合器定义的任何函数都会导致堆栈溢出。
相反,您需要使用Y-combinator的应用顺序版本(let y f =
let f' (Rec x as rx) = f (x rx)
f' (Rec f')
):
\f.(\x.f(\y.(x x)y))(\x.f(\y.(x x)y))
另一种选择是使用显式懒惰来定义正常的Y组合子:
let y f =
let f' (Rec x as rx) = f (fun y -> x rx y)
f' (Rec f')
这样做的缺点是递归函数定义现在需要一个惰性值的显式强制(使用type 'a Rec = Rec of ('a Rec -> 'a Lazy)
let y f =
let f' (Rec x as rx) = lazy f (x rx)
(f' (Rec f')).Value
属性):
Value
但是,它具有以下优点:您可以使用惰性语言定义非函数递归值:
let factorial = y (fun f -> function | 0 -> 1 | n -> n * (f.Value (n - 1)))
作为最后的替代方案,您可以尝试通过使用拳击和向下转换来更好地逼近无类型的lambda演算。这会给你(再次使用Y-combinator的应用顺序版本):
let ones = y (fun ones -> LazyList.consf 1 (fun () -> ones.Value))
这有明显的缺点,它会导致不需要的装箱和拆箱,但至少这完全是内部的实现,并且永远不会在运行时导致失败。
答案 1 :(得分:10)
我会说这是不可能的,并且问为什么,我会手动并调用简单输入lambda演算的事实normalization property。简而言之,简单类型的lambda演算的所有项都终止(因此Y不能在简单类型的lambda演算中定义)。
F#的类型系统并不完全是简单类型lambda演算的类型系统,但它足够接近。没有let rec
的F#非常接近简单类型的lambda演算 - 而且,重申一下,在那种语言中你不能定义一个不终止的术语,并且不包括定义Y.
换句话说,在F#中,“let rec”至少需要是一个语言原语,因为即使你能够从其他原语定义它,你也无法输入这个定义。将它作为原语允许您(除其他外)为该原语提供特殊类型。
编辑:kvb在他的回答中表明,类型定义(简单类型的lambda演算中缺少的一个特征,但存在于let-rec-less F#中)允许得到某种递归。非常聪明。答案 2 :(得分:4)
ML衍生案中的Case和let语句是Turing Complete的原因,我相信它们基于System F而不是简单的输入,但重点是相同的。
系统F找不到任何定点组合子的类型,如果可以的话,它不会强烈正常化。
强正规化意味着任何表达式都具有一个正常形式,其中正常形式是一个无法进一步缩小的表达式,这与无类型表达式不同,其中每个表达式都具有 at max 一个普通形式,它也可以没有正常形式。
如果类型化的lambda calculi可以以任何方式构造一个固定点运算符,那么表达式很可能没有正常形式。
另一个着名的定理,即停机问题,意味着强烈规范化的语言不是图灵完整的,它说不可能决定(不同于证明)图灵完整语言的程序的哪个子集将停止输入什么。如果一种语言正在强烈规范化,那么如果它停止,则它是可判定的,即总是停止。我们决定这个的算法是程序:true;
。
为了解决这个问题,ML衍生产品扩展了System-F with case和let(rec)来克服这个问题。因此,函数可以再次在它们的定义中引用它们,使它们实际上不再是lambda演算,不再可能单独依赖匿名函数来处理所有可计算函数。因此,他们可以再次进入无限循环并恢复他们的图灵完整性。
答案 3 :(得分:2)
简短的回答:你不能。
答案很长: 简单类型的lambda演算强烈正常化。这意味着它不是图灵的等价物。其原因基本上归结为Y组合子必须是原始的或递归定义的(如您所见)。它根本无法在系统F(或更简单的类型计算)中表达。没有办法解决这个问题(毕竟已经证明了这一点)。您可以实施的Y组合器可以按照您想要的方式工作。
如果你想要一个真正的教堂式Y组合器,我建议你试试方案。使用上面给出的应用版本,因为其他版本将无法使用,除非您明确添加惰性,或使用惰性Scheme解释器。 (在技术上,Scheme不是完全无类型的,但是它是动态类型的,这对此来说已经足够了。)
为了证明强正常化,请参见此处: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.127.1794
在考虑了一些之后,我很确定添加一个原始的Y组合子,其行为与letrec定义的方式完全相同,使得System F图灵完成。您需要做的就是模拟图灵机然后将磁带实现为整数(以二进制解释)和移位(定位头)。
答案 4 :(得分:0)
简单地定义一个函数,将它自己的类型作为记录,就像在 Swift 中一样(有一个结构体):)
这里,Y(大写)在语义上被定义为一个可以用它自己的类型调用的函数。在 F# 术语中,它被定义为包含名为 call 的函数的记录,因此要调用定义为这种类型的 y,您必须实际调用 y.call :)
all:
hosts:
AAA-BAT1:
Fabric: AAA
Role: BGW
Type de materiel: N9K-C9YTR
emplacement:
Hauteur: '20'
Salle: '1'
Travee: '4'
BBB-BAT2:
Fabric: BBB
Role: SP
Type de materiel: N9K-C9YTR
emplacement:
Hauteur: '20'
Salle: '1'
Travee: '4'
阅读起来不是非常优雅,但它不会诉诸递归来定义应该自己提供递归的 y 组合器 ^^