【问题标题】:how to partially implement function in sml如何在 sml 中部分实现功能
【发布时间】:2021-04-07 16:42:51
【问题描述】:

我正在阅读 Modern Compiler Implementation in ML 一书,同时学习 sml。

我被第一章中的一个练习(1.1.b)难住了。 我们被要求实现一个维护键/值对的二叉树, 键是字符串,值是参数化类型。 我的数据类型定义如下

type key = string
datatype 'a tree = LEAF | TREE of 'a tree * key * 'a * 'a tree

我们被要求实现一个类型为'a tree * key -> 'alookup 函数。 我不知道如何实现这个函数,因为我不知道当树是LEAF 时要返回什么。 'a 没有默认值。书中的说明并没有说明找不到密钥的方法,但它坚持函数必须返回类型'a

这只是书中的一个错误,还是在 sml 中有正确的方法来做到这一点?

在这种情况下,我试图引发异常,但编译器显然不会让我在不捕获异常的情况下引发异常。

如果我在 Scala 中实现此功能,我会将返回类型更改为 Option[A],如果找不到密钥则返回 None;或者在 Common Lisp 中,我会传递一个延续来调用找到的值,然后如果找不到键,则根本不调用延续。

这是我的代码,还没有工作。

(* page 12, exercise 1.1 b *)
type key = string
datatype 'a tree = LEAF | TREE of 'a tree * key * 'a * 'a tree

val empty = LEAF

fun insert(key,value,LEAF) = TREE(LEAF,key,value,LEAF)
  | insert(key,value,TREE(l,k,v,r)) =
    if key<k
    then TREE(insert(key,value,l),k,v,r)
    else if key>k
    then TREE(l,k,v,insert(key,value,r))
    else TREE(l,key,value,r)

fun lookup(LEAF,key) =  (* !!!HELP!!!  I don't know what to do in this case *)
  | lookup(TREE(l,k,v,r),key) = if k=key
                                then v
                                else if key<k
                                then lookup(l,key)
                                else lookup(r,key)

val t1 = insert("a",1,empty)
val t2 = insert("c",2,t1)
val t3 = insert("b",3,t2)
   ;

     lookup(t3,"a");
     lookup(t3,"b");
     lookup(t3,"c");
     lookup(t3,"d");

顺便说一句,我不明白为什么 emacs sml-mode 坚持缩进对 lookup 的调用。

【问题讨论】:

  • 这只是一个基本的练习练习;让它以不匹配的模式失败。
  • re:“让它以不匹配的模式失败”?我明白了,错误消息只是来自我的测试用例lookup(t3,"d"),而不是来自编译器。好的,谢谢,我相信我明白了。
  • 如果你真的想要返回类型'a 并且不引发异常,你可以永远循环:)。尽管正如您所建议的那样,我可能只会在实践中使用选项类型。我确实有一个想法是你的if 链可能会更好地替换为String.compare (k, key) 上的套管。

标签: sml ml


【解决方案1】:

您绝对可以将lookup 更改为使用'a option

这是一个使用不透明模块和类型参数而不是模块参数的基本字典实现。您从中派生的示例使用混合,其中key 是模块参数,'a 是值仍然是多态的。这通常是一个好主意,因为您在实施方面具有更大的灵活性。但是由于您只是将列表用作字典,因此您可以使用''key 来表示一个可比较相等的类型参数。 (如果您想要更高效的表示,例如树或哈希图,则需要更大的接口。)

signature DICT = sig
  type (''key, 'value) dict
  val empty : (''key, 'value) dict
  val insert : ''key -> 'value -> (''key, 'value) dict -> (''key, 'value) dict
  val lookup : ''key -> (''key, 'value) dict -> 'value option
end

structure Dict :> DICT = struct
  type (''key, 'value) dict = (''key * 'value) list
  val empty = []
  fun insert k v [] = [(k, v)]
    | insert k v ((k2,v2)::dict) =
        if k = k2
        then (k,v)::dict
        else (k2,v2)::insert k v dict
  fun lookup k dict = Option.map #2 (List.find (fn (k2,v2) => k = k2) dict)
end

现在,

- val demo = Dict.lookup "foo" (Dict.insert "foo" 42 Dict.empty)
> val demo = SOME 42 : int option

val t1 = insert("a",1,empty)
val t2 = insert("c",2,t1)
val t3 = insert("b",3,t2)
   ; X

     lookup(t3,"a");
     lookup(t3,"b");
     lookup(t3,"c");
     lookup(t3,"d");

顺便说一句,我不明白为什么 emacs sml-mode 坚持缩进查找调用。

这是因为 sml-mode 认为 ; 是表达式运算符而不是声明分隔符。我在其中放置了一个X,如果它表达式运算符,那么在这里放置一个表达式是很自然的。并且进行换行,将随后​​的表达式放在同一个缩进点上是很自然的。

Emacs sml-mode 仅使用前一行来确定缩进级别,这使得它有些简单。您可以通过在所有声明前加上 val ... = 或以不那么随意的方式放置 ; 来避免在此处与缩进作斗争,例如

...
val t3 = insert ("b", 3, t2)
val L1 = lookup(t3,"a")
val L2 = ...

...
val t3 = insert ("b", 3, t2)
;lookup(t3,"a");
;lookup(t3,"b");

这里双方;s 背后的想法是,当我们谈论声明分隔符(而不是表达式运算符)时,您可以任意重复许多;s,因此,因为@987654337 @s 不需要终止 val ... 声明,这些行不容易在前后更改上下文,就像 val x = ...。仅当您仅将 SML 代码作为脚本 / 在 REPL 中评估时,这种风格才真正有意义。否则,如果您只对副作用感兴趣,您可能想要命名您的绑定或使用 val _ = ...

【讨论】:

    猜你喜欢
    • 2011-04-25
    • 2018-06-23
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    • 2016-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多