我在这个问题中引用了代码:如何在OCaml中将字典实现为函数?,我用OCaml编写了函数字典代码:
type key = string;;
type 'a dict = key -> 'a option;;
let add (d : 'a dict) (k, v) : 'a dict = fun k' -> if k' = k then v else d k';;
let empty () = ... ;;
let find d k = ... ;;
在此代码中,我想知道的是 add 函数的工作原理。变量k'
是什么意思?当我们使用关键字时fun
例如,let sub x = fun y -> x - y;;
,fun
旁边的变量表示参数。对吗?当我想使用我定义的sub
函数时,我们可以调用该函数
# sub 1 2;;
- : int = -1
2
是绑定到y
的参数, 是fun
关键字旁边的变量。但是,在上面的字典代码中,add
函数中fun
关键字旁边有变量k'
,但它不代表任何其他参数。调用add
函数所需的参数是'a dict
类型变量和(k, v)
类型元组。不需要第三个参数。那么,k'
是什么意思呢?我不知道。
在 OCaml 中,
let sub x y = x - y
是 的速记符号(句法糖)
let sub = fun x -> fun y -> x - y
为了得到它,让我们选择一个更简单的例子,
let succ x = x + 1
是较短的句法形式
let succ = fun x -> x + 1
这表示我们将名称succ
绑定到函数fun x -> x + 1
,其中后者是函数文字。编程语言中的文字是用于定义值的语法表示法。例如,对于整数,我们有一组有限的文字1
、2
、3
等。对于字符串,我们有一组几乎无限的文字,例如"hello"
、""
、"etc"
。OCaml 中的函数也是一个值,与整数或字符串相同,因此它具有函数文字的语法,即
fun <arg> -> <body>
它创建一个函数,该函数的计算结果为<body>
值,其中所有空闲出现的<arg>
都被替换为实际传递的参数。
您可能已经注意到,我们实际上只能在函数文字中绑定一个参数。事实上,OCaml 中的所有函数都是一元的。你可能已经知道你可以定义像fun x y -> x - y
这样的函数,但这也只是fun x -> fun y -> x - y
的语法糖。那么后一种符号是什么意思呢?让我们用括号来了解这个函数是如何计算的:
fun x -> (fun y -> x - y)
因此,我们可以识别fun x -> <body>
部分,其中<body>
是自身的函数,即<body> = fun y -> x - y
.这意味着表达式fun x -> fun y -> x - y
是一个函数,它接受一个参数x
并返回另一个函数,它接受一个参数y
并返回x-y
。这种定义函数的方式,即,当不是接受元组的函数,而是有一个返回函数的函数,依此类推,称为currying。
现在,当我们对柯里的想法感到满意并且 OCaml 中的所有函数实际上都是一个参数时,我们可以回到函数字典的原始示例。
添加函数
let add (d : 'a dict) (k, v) : 'a dict = fun k' -> if k' = k then v else d k'
去除(不必要的)类型批注
let add d (k, v) = fun k' -> if k' = k then v else d k'
然后以使柯里明确的形式表示,是
let add = fun d -> fun (k, v) -> fun k' -> if k' = k then v else d k'
因此,对于给定的字典,它是一个函数,d
生成一个函数,该函数接受一对(k,v)
并返回一个函数,对于所有等于k
的k'
,该函数将返回d
。如果键不同,则它将键应用于第一个参数d
。让我们更深入地了解
fun k' -> if k' = k then v else d k'
字面。正如我们所看到的,变量d
并且k
在该函数中自由出现,即它们不受参数k
的约束。当函数引用外部作用域(未在模块级别定义)中的变量时,将创建一个闭包。闭包是一个不透明的对象,其中包含指向代码1的指针(在我们的例子中,它是if k' = k then v else d k'
和指向每个捕获变量(即,指向从外部作用域获取的每个变量)的指针。在我们的例子中,它(大致)创建一个记录,其中包含三个条目,d
、k
和代码。经过几个小时的调解,不难看出,函数字典只是闭包的单链列表,因为d
是指向闭包的指针,而闭包又包含指向另一个闭包的指针,直到我们到达指向empty
的指针,无论键如何,它都会返回None
(并且在您的示例中编码不正确, 因为它应该期望任何键,而不是类型单位的值)。
综上所述并牢记,我们现在可以编写add
函数的速记符号:
let add d k k' = if k' = k then v else d k'
它具有与任何其他符号完全相同的语义(即含义,行为),因为它只是句法糖。
1)为了防止任何混淆,代码本身永远不会存储在堆内存中。即使您有一个不绑定到任何名称的匿名函数,例如,(fun x y -> x + y) 2 3
编译器也会发出代码并将其存储在二进制文件的文本部分中,并给它一些有趣的名称。所以上面的代码将只是对名称混乱的函数的正常调用,例如call Testing_fun001
。回到函数字典示例,每个条目将是一个包含三个单词的对象,键,数据,指向下一个条目的指针。与关联列表基本相同的基础表示形式。
关于您的类型定义..
type key = string;;
type 'a dict = key -> 'a option;;
为什么不呢。。。
type 'a dict = 'a -> 'a option;;
甚至更好...
type ('a, 'b) dict = 'a -> 'b option;;