如何在OCaml中实现字典作为函数?

时间:2012-12-04 17:38:10

标签: functional-programming ocaml

我正在学习Jason Hickey's Introduction to Objective Caml


这是一个我没有任何线索的练习

enter image description here enter image description here


首先,将dictionary作为function实施是什么意思?我怎么形象?

我们需要array或类似的东西吗?显然,我们不能在此练习中使用数组,因为array中尚未引入Chapter 3。但是How do I do it without some storage?

所以我不知道该怎么做,我希望得到一些提示和指南。

4 个答案:

答案 0 :(得分:9)

我认为这个练习的目的是让你使用闭包。例如,请考虑文件fun-dict.ml中的以下一对OCaml函数:

let empty (_ : string) : int = 0
let add d k v = fun k' -> if k = k' then v else d k'

然后在OCaml提示符下你可以这样做:


# #use "fun-dict.ml";;
val empty : string -> int = 
val add : ('a -> 'b) -> 'a -> 'b -> 'a -> 'b = 
# let d = add empty "foo" 10;;
val d : string -> int = 
# d "bar";;  (* Since our dictionary is a function we simply call with a
                  string to look up a value *)
- : int = 0   (* We never added "bar" so we get 0 *)
# d "foo";;
- : int = 10  (* We added "foo" -> 10 *)

在此示例中,字典是对string值的int键的函数。 empty函数是将所有键映射到0的字典。 add函数创建一个闭包,它接受一个参数,一个键。请记住,我们这里对字典的定义是从键到值的函数,所以这个闭包是一个字典。它会检查k'(闭包参数)是否为= k,其中k是刚刚添加的密钥。如果是,则返回新值,否则调用旧字典。

通过关闭链中的下一个字典(函数),你实际上有一个闭包列表,这些闭包不是由cons单元链接的。)

额外练习,你会如何从这本词典中删除一个键?


编辑:什么是封闭?

闭包是一个从它创建的范围引用变量(名称)的函数。那么这是什么意思?

考虑我们的add功能。它返回一个函数

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

如果您只查看该函数,则有三个未定义的名称,dkv。要弄清楚它们是什么,我们必须查看封闭范围,即add的范围。我们找到的地方

let add d k v = ...

因此,即使add返回了一个新函数,该函数仍引用要添加的参数。因此,闭包是一个必须由某个外部范围关闭才能有意义的函数。

答案 1 :(得分:7)

在OCaml中,您可以使用实际函数来表示字典。非FP语言通常不支持作为一等对象的函数,所以如果你习惯了它们,你可能一开始就不会这么想。

字典地图,这是一个功能。想象一下,你有一个函数d,它接受​​一个字符串并返回一个数字。它为不同的字符串返回不同的数字,但对于相同的字符串总是相同的数字。这是一本字典。字符串是你正在查找的东西,你得到的数字是字典中的相关条目。

您不需要数组(或列表)。您的add函数可以构造一个函数,在没有任何(显式)数据结构的情况下执行必要的操作。请注意,add函数接受字典(函数)并返回字典(新函数)。

要开始考虑高阶函数,这是一个例子。函数bump采用函数(f: int -> int)和int(k: int)。它返回一个新函数,该函数返回的值k大于f为同一输入返回的值。{/ p>

let bump f k = fun n -> k + f n

(关键是bumpadd一样,接受一个函数和一些数据,并根据这些值返回一个新函数。)

答案 2 :(得分:5)

我认为值得一提的是,OCaml中的函数不仅仅是代码片段(与C,C ++,Java等不同)。在那些非函数式语言中,函数没有与它们相关联的任何状态,谈论这样的事情会有点荒谬。但是功能语言中的函数并非如此,您应该开始将它们视为一种对象;一种奇怪的物体,是的。

那么我们如何“制造”这些物品呢?让我们以Jeffrey为例:

let bump f k = 
  fun n ->
    k + f n

现在bump实际上做了什么?它可能会帮助您将bump视为您可能已经熟悉的构造函数。它构造了什么?它构造了一个函数对象(在这里非常失败)。那么结果对象有什么状态?它有两个实例变量(排序),分别是fk。当您调用bump f k时,这两个实例变量将绑定到生成的函数对象。您可以看到返回的函数对象:

fun n ->
  k + f n

在其正文中使用这些实例变量fk。返回此函数对象后,您只能调用它,没有其他方法可以访问fk(因此这是封装)。

使用术语功能对象非常罕见,它们只被称为功能,但您必须记住它们也可以“封闭”状态。这些函数对象(也称为闭包)与面向对象编程语言中的“真实”对象差别不大,可以找到一个非常有趣的讨论here

答案 3 :(得分:1)

我也在努力解决这个问题。这是我的解决方案,适用于教科书中列出的案例......

空字典只返回0:

let empty (k:string) = 0

在键上查找字典的函数。这个功能很简单:

let find (d: string -> int) k = d k

添加扩展字典的功能以具有另一个条件分支。我们返回一个新的字典,它接受一个密钥k'并将其与k匹配(我们需要添加的密钥)。如果匹配,我们返回v(相应的值)。如果它不匹配,我们返回旧的(较小的)字典:

let add (d: string -> int) k v =
    fun k' ->
        if k' = k then
                v
            else
                d k'

您可以更改添加以具有删除功能。此外,我添加了一个条件,以确保我们不删除不存在的密钥。这只是为了练习。无论如何,字典的实现很糟糕:

let remove (d: string -> int) k =
    if find d k = 0 then
        d
    else
        fun k' ->
            if k' = k then
                    0
                else
                    d k'

我对术语不太满意,因为我还在学习函数式编程。所以,请随时纠正我。