在OCaml字典的功能实现中添加函数



我在这个问题中引用了代码:如何在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,其中后者是函数文字。编程语言中的文字是用于定义值的语法表示法。例如,对于整数,我们有一组有限的文字123等。对于字符串,我们有一组几乎无限的文字,例如"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)并返回一个函数,对于所有等于kk',该函数将返回d。如果键不同,则它将键应用于第一个参数d。让我们更深入地了解

一下
fun k' -> if k' = k then v else d k'

字面。正如我们所看到的,变量d并且k在该函数中自由出现,即它们不受参数k的约束。当函数引用外部作用域(未在模块级别定义)中的变量时,将创建一个闭包。闭包是一个不透明的对象,其中包含指向代码1的指针(在我们的例子中,它是if k' = k then v else d k'和指向每个捕获变量(即,指向从外部作用域获取的每个变量)的指针。在我们的例子中,它(大致)创建一个记录,其中包含三个条目,dk和代码。经过几个小时的调解,不难看出,函数字典只是闭包的单链列表,因为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;;

最新更新