【问题标题】:How to implement a dictionary as a function in OCaml?如何在 OCaml 中将字典实现为函数?
【发布时间】:2012-11-22 10:06:23
【问题描述】:

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


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


首先,将dictionary 实现为function 是什么意思?我怎样才能看到它?

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

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

【问题讨论】:

  • 你可以使用列表吗?可能是 (string, int) 对的列表可以作为您的存储空间?
  • 好的,我检查了你的参考资料。您不应该为此使用 (string, int) 列表。 Jeffrey 已为您指明了答案。
  • 你能给我们这个参考吗?我想在上下文中查看问题以了解不允许的内容。杰弗里是谁,每个人都在提到,作者、老师等?

标签: functional-programming ocaml


【解决方案1】:

我认为这个练习的目的是让你使用闭包。例如,考虑文件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 返回了一个新函数,该函数仍然引用要添加的参数。所以闭包是一个函数,它必须被一些外部作用域封闭才能有意义。

【讨论】:

  • 好的,在Java中,我们只使用一个dict对象来存储所有的{key, value}。所以,在这里,我为每个 {key,value} 创建一个字典?
  • 对,在 Java 中,您的 single 字典在每次添加或删除元素时都会被破坏性地修改。在 OCaml 和相关的函数式语言中,典型的习惯用法是你得到一个新的数据结构,而原来的数据结构仍然完好无损。以列表为例,您以[] 开头,为空列表。然后你做let x = 5 :: [],你没有破坏性地修改空列表以包含5,你已经创建了一个包含5的新列表。现在你做let y = 6 :: x,同样你没有修改x。您仍然拥有x = [5],并且您现在拥有一个新列表y,其中y = [6; 5]。在 Java 中 x 已更改。
  • 好的,谢谢。在这个 OCaml dict 示例中,如果我们真的想找到一个键的值,我们必须遍历我们创建的所有 dict?
  • 这取决于你的意思。如果您问我们是否必须同时查看我的示例中的xy,不是真的。如果我们想知道某个键是否在x 中,那么我们只在x 中查找,我们的优势在于知道现在其他人从我们下面修改了x(例如在另一个线程中)。如果您问我们是否必须在所有字典中查找到链的末尾,是的,这是真的,我们的查找是最坏的情况O(n) 其中 n 是元素的数量。实际上,您永远不会真正实现这样的字典,而是使用某种树。
  • 您好,我相信您对add 的实现是错误的。它应该是 d k' 而不是 d k!
【解决方案2】:

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

字典一个映射,它是一个函数。想象一下,你有一个函数d,它接受一个字符串并返回一个数字。它为不同的字符串返回不同的数字,但对于相同的字符串总是返回相同的数字。这是一本字典。字符串是您要查找的内容,而您返回的数字是字典中的相关条目。

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

要开始考虑高阶函数,这里有一个示例。函数 bump 接受一个函数 (f: int -> int) 和一个 int (k: int)。它返回一个新函数,该函数返回的值大于 f 为相同输入返回的值。

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

(重点是bumpadd 一样,接受一个函数和一些数据,然后根据这些值返回一个新函数。)

【讨论】:

  • 感谢您的回答。您的bump 示例更容易理解,因为它不存储任何统计信息。但是我怎样才能对dict 进行成像呢? dictionary 正在存储所有 {key, value}s,对吗?
  • 好的,在 Java 中,我们只使用 one dict 对象来存储所有的 {key, value}。所以,在这里,我为每个 {key,value} 创建一个字典?
  • 在某种意义上这是真的。在纯 FP 中,您永远不会修改数据。您只需创建新数据。但是,垃圾收集器会清理所有你不使用的旧字典。作为回报,您永远不必担心踩到别人的数据。这种方法有一些成本,但效果非常好。
  • 但重点是,如果我有 dict1 和 "key1", 0,那么在某个时候,gc 会在一段时间后擦除它?
  • 是的,但这取决于您是否可以“看到”价值,而不是准时。只要名称存在,您可以谈论(具有名称)的任何值都将始终存在。所以你不必担心。
【解决方案3】:

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

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

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

现在bump 实际上做了什么?将bump 视为您可能已经熟悉的构造函数 可能会对您有所帮助。它构建什么?它构造了一个函数对象(这里说得很委婉)。那么生成的对象有什么状态?它有两个实例变量(有点),它们是fk。当您调用bump f k 时,这两个实例变量绑定到生成的函数对象。可以看到返回的function-object:

fun n ->
  k + f n

在其主体中使用这些实例变量fk。一旦这个函数对象返回,你就只能调用它,没有其他方法可以访问fk(所以这是封装)。

function-object 一词很少见,它们仅称为 functions,但您必须记住,它们也可以“包围”状态.这些函数对象(也称为闭包)与面向对象编程语言中的“真实”对象相距不远,可以找到一个非常有趣的讨论here

【讨论】:

  • 好的,在Java中,我们只使用一个dict对象来存储所有的{key, value}。所以,在这里,我为每个 {key,value} 创建一个字典?
  • 单个(key, value, f) 条目可以像上面那样包装在函数对象中。 f 将是另一个函数对象,其中包含下一个 (key, value, f) 条目……依此类推,直到最后一个条目 (key, value, empty)。在每个add 调用中,这个条目(即字典)的list(隐式)将被扩展一个。如果你在这个问题上花上几天的时间,你肯定会学到很多东西!!!
  • 谢谢,实际上,除了问题之外,我现在正在把头撞到墙上。所以我将用英语描述我目前的想象。就像add dict1 k1 v1 返回dict2,然后add dict2 k2 v2 返回dict3...等等。插入 10 个 k,v 对后,我们得到 dict10? 那我们如何找到密钥呢?dict10开始?
  • 有点像linkedlist?
  • @JacksonTale:所有三个答案都是正确的 :) 您应该选择对您帮助最大的一个。我们三个人都避免给你确切的答案,因为我们希望你自己弄清楚。我想你已经明白了,这才是最重要的!!!
【解决方案4】:

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

空字典只返回 0:

let empty (k:string) = 0

Find 在键上调用字典的函数。这个函数很简单:

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

Add 扩展了字典的功能以具有另一个条件分支。我们返回一个带有键 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'

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-07
    • 2021-08-29
    • 1970-01-01
    相关资源
    最近更新 更多