Lambda 为什么'id'不是OCaml中的值?

Lambda 为什么'id'不是OCaml中的值?,lambda,functional-programming,ocaml,lambda-calculus,value-restriction,Lambda,Functional Programming,Ocaml,Lambda Calculus,Value Restriction,我仍在努力理解OCaml中的值限制,我正在通读。在它的状态中,(funx->x)(funy->y)不是一个语法值,同时它还声明lambda表达式应该是一个值。我在这里有点困惑,id在本质上不是也是一个lambda表达式吗?在OCaml中,什么才是真正的语法值 我还在utop中进行了尝试,发现了以下几点: utop # let x = let x = (fun y -> y) (fun z -> z) in x ;; val x : '_a -> '_a = <fun&g

我仍在努力理解OCaml中的值限制,我正在通读。在它的状态中,
(funx->x)(funy->y)
不是一个语法值,同时它还声明lambda表达式应该是一个值。我在这里有点困惑,
id
在本质上不是也是一个lambda表达式吗?在OCaml中,什么才是真正的语法值

我还在
utop
中进行了尝试,发现了以下几点:

utop # let x = let x = (fun y -> y) (fun z -> z)  in x ;;
val x : '_a -> '_a = <fun>
在这里,
id a
似乎被视为一个值


它们都是函数应用,有什么区别

这是一个应用程序,不是lambda表达式。左表达式是函数,右表达式是应用函数的值


值的概念(在值限制的意义上)是一个语法概念。这和价值的类型无关。

所以,这里涉及两个概念:让多元性和价值限制。Let多态性不允许对所有未被Let绑定的值进行类型泛化。或者,在不使用双重否定的情况下,它只允许在let绑定中引入多态值。这是一种过度近似,也就是说,它可能不允许有效的程序(存在假阳性),但它永远不会允许无效的程序(它将保持可靠性)

值限制是另一种过度近似,需要它来保持命令式程序的可靠性。它不允许非语法值的多态性。OCaml使用了一个更精确的过度近似版本,称为a(它实际上允许某些非语法值是多态的)

但首先让我解释什么是语法值:

非正式地,一个语法值是一个可以在不做任何计算的情况下进行评估的表达式,例如,考虑下面的让绑定:

let f = g x 
这里的
f
不是一个语法值,因为为了获得该值,需要计算表达式
gx
。但是接下来呢,

let f x = g x
f
是语法上的,如果我们去掉糖,它会更明显:

let f = fun x -> g x
现在很明显,
f
是语法的,因为它绑定到lambda表达式

该值被称为syntarchy,因为它直接在程序中定义(在语法中)。基本上,它是一个可以在静态时间计算的常量值。稍微正式一点,以下值被视为语法:

  • 常量(如整数和浮点文字)
  • 仅包含其他简单值的构造函数
  • 函数声明,即以fun或Function开头的表达式,或等效的let绑定,
    let f x=…
  • expr2中的let var=expr1形式的let绑定,其中expr1和expr2都是简单值
现在,当我们非常确定什么是语法什么不是语法时,让我们更仔细地看一下您的示例。让我们从Wright的例子开始,实际上:

let f = (fun x => x) (fun y => y)
或者,通过引入
let id=fun x->x

let f = id id
您可能会看到,
f
这里不是语法值,尽管
id
是语法值。但是为了得到
f
的值,您需要进行计算——因此该值是在运行时定义的,而不是在编译时定义的

现在,让我们来举例说明:

let x a = let x = (fun y -> y) a in x
==>
let x = fun a -> let x = (fun y -> y) a in x 
我们可以看到,
x
是一个语法值,因为左边是一个lambda表达式。lambda表达式的类型是
'a->'a
。您可能会问,为什么表达式的类型不是
'\u a->'\u a
。这是因为值限制仅在顶层引入,而lambda表达式还不是值,它是一个表达式。在外行术语中,首先,在没有副作用的假设下推断出最普遍的Hindley-Milner类型,然后通过(放松的)值限制削弱推断出的类型。类型推断的范围是
let
绑定

这都是理论,有时并不清楚为什么有些表达式类型良好,而语义相同但编写略有不同的表达式类型不好。直觉可能会说,这里有问题。是的,事实上,
let f=id
是一个格式良好的程序,被类型检查器拒绝,这是一个过度近似的例子。如果我们将这个程序转换为
let f x=id id x
,它会突然变成一个具有一般类型的类型良好的程序,尽管转换不会改变语义(而且两个程序实际上编译为相同的机器代码)。这是类型系统的一个限制,它是简单性和精确性之间的折衷(稳健性不能成为折衷的一部分-typechecker必须是稳健性的)。因此,从理论上看,后一个例子为什么总是安全的,这是完全不明显的。为了实验起见,让我们试着使用您的示例,并尝试打破程序:

# let x = fun a -> let z = ref None in let x = (fun y -> z := Some y; y) a in x ;;
val x : 'a -> 'a = <fun>
#让x=fun a->让z=ref-None在让x=(fun y->z:=Some y;y)a在x;;
val x:'a->'a=

因此,我们在这里添加了一个reference
z
,我们试图存储该值,这样在不同类型的不同应用程序下,我们应该能够存储不同类型的相同引用值。然而,这是完全不可能的,因为
x
是一个语法值,所以可以保证,每个类型
xk
都被称为一个新的引用,并且该引用永远不会泄漏let定义的范围。希望这有帮助:)

utop#let x a=let x=(fun y->y)a in x;;val x:'a->'a=
这里
(fun y->y)a
也是一个函数应用程序,但它通过了值限制。为什么?它在语法上不是一个应用程序。从语法上讲,它是一个lambda表达式
letxa=
letx的奇特语法=
# let x = fun a -> let z = ref None in let x = (fun y -> z := Some y; y) a in x ;;
val x : 'a -> 'a = <fun>