OCaml合并排序错误?

时间:2011-08-26 05:40:02

标签: syntax ocaml mergesort

我正在学习OCaml,它给了我一些问题。我正在尝试实现merge_sort函数,但它在给定代码的第5行上不断给出错误。我只是完全混淆为什么它给我错误,如果有人能启发我,我会非常感激。我甚至不确定我是否正确设置模式匹配(匹配语句),所以如果你能看一下,那真的很有帮助。

let rec merge_sorted (l1:int list) (l2:int list) : int list =
let end_list = [] in
begin match l1, l2 with 
    | [], [] -> end_list
    | h1::t1, h2::t2 -> if h1 < h2 then merge_sorted t1 t2 (h1 :: end_list) else merge_sorted t1 t2 h2::end_list, h1::end_list
end

我得到的错误是:

  

错误:此函数适用于太多参数,   也许你忘记了一个`;'   merge_sorted:int list - &gt; int list - &gt; int list

在说“如果h1&lt; h2然后merge_sorted t1 t2 ......”的部分

我还想知道是否有学习OCaml语法的地方?我一直在尝试使用Jason Hickey的书,但有些事情并没有深入探讨(例如这种多重/并行模式匹配)。我只用Java编写,所以在OCaml中编码对我来说是一种令人沮丧的新体验。

2 个答案:

答案 0 :(得分:6)

您已将merge_sorted声明为类型int list -> int list -> int list的函数,或者将两个int列表作为参数并返回另一个int列表的函数。

您遇到的问题是因为您使用三个 merge_sorted参数调用int list

h1::t1, h2::t2 -> if h1 < h2 then merge_sorted t1 t2 (h1 :: end_list) else merge_sorted t1 t2 h2::end_list, h1::end_list

具体来说, merge_sorted t1 t2(h1 :: end_list)。您没有为函数定义中的结果列表提供任何条件。

您需要修改您的功能定义,可能是这样:

let rec merge_sorted l1 l2 results =
  (* code code code *)

关于你的第二个问题,可以找到一套不错的教程here

修改

回应你的评论 -

首先,::运算符有两个目的。一,它创建一个新的列表,其中包含一个新的头部和一个现有的尾部 - 1 :: [2]会产生[1; 2]。其次,它在模式匹配期间分解现有列表 - match [1; 2] with x :: xsx绑定到1y绑定到[2]

这一点正在得到解决,你的方法中有一些东西可以在Java中正常工作,但在OCaml中根本不起作用。首先,在函数的每次递归调用中,您将end_list重新声明为空列表。

其次,因为你的列表连接与递归调用merge_sorted在同一行上,编译器将(别无选择)认为你正在指定第三个函数调用。你的意思是:

h1::t1, h2::t2 -> if h1 < h2 then
                      merge_sorted t1 t2
                      h1 :: end_list
                  else
                      merge_sorted t1 t2
                      h2 :: end_list
                      h1 :: end_list

BUT!这不是你的解决方案,请继续阅读。

第三,更重要的是,OCaml不会这样工作。默认情况下,OCaml列表(实际上所有变量)都是不可变的 - 也就是说,一旦将值绑定到1,就无法更改它。当你说(h1 :: end_list)时,你没有改变结束列表;你真正在做的是创建一个 new 列表,其中h1为头,end_list为尾。因为你将这一切都放在同一行上,编译器认为你在​​Java-land中所做的merge_sorted(t1, t2, new List(h1, end_list))相当于merge_sorted(List t1, List t2)

由于您无法就地修改列表,因此此条带的函数式语言具有不同的列表处理惯用语。在这种情况下,大多数函数式程序员都会定义其函数来获取额外的 accumulator 参数。例如:

let rec map f data acc =
    match data with
    | []      -> List.rev acc
    | x :: xs -> map f xs ((f x) :: acc);;

map (fun x -> x + 1) [1; 2; 3;] [];;  (* yields [2; 3; 4] *)

这是规范map函数,它接受列表和函数,将列表中的每个项目应用于该函数,并返回包含结果的新列表。如您所见,将data中的每个项目应用于f的结果存储在acc中,结果将传递到下一个调用,仅限于data为空时返回。通过这种方式,我们可以实现一系列的列表修改,而无需改变变量。

如果您不喜欢函数签名中的额外参数,可以将其隐藏在嵌套函数中,如下所示:

let map f data =
    let rec loop d acc =
        match d with
        | []      -> List.rev acc
        | x :: xs -> loop xs ((f x) :: acc)
    in
        loop data []

我希望这对你有所帮助 - 从命令性到功能性习语的过渡可能真正让人心旷神色,对于那些刚接触它的人来说,不变性似乎是一种残酷和任意的限制 - 我保证功能的好处和美感编程显示给你,如果你坚持下去的话!

答案 1 :(得分:3)

Ben的答案很好,但我希望你不会介意几个额外的评论。首先,您的模式匹配并非详尽无遗。它不包括其中一个列表为空而另一个列表为空的情况。其次,如果您只是在学习OCaml,我首先要以非尾递归的方式编写合并。也就是说,我不会将累积结果作为参数传递。你最终会得到类似这样的东西:

let rec merge_sorted l1 l2 =
    match l1, l2 with
    | (* Either one is empty *) -> (* answer is pretty obvious *)
    | h1::t1, h2::t2 ->
          if h1 < h2 then h1 :: (* Merge the rest *)
          else h2 :: (* Merge the rest *)

(我希望这不会给你带来太多的乐趣,并从中获取乐趣。)

<强>(添加)

在代码块中:“然后h1 ::(*合并其余的*)”,这样做::允许你调用其他函数吗?

好吧,如果你说(xxx) :: (yyy),你可以在xxxyyy部分中拥有你想要的任何函数调用(如果它们在类型上有意义的话)。这实际上是函数式编程的本质:它可以通过函数调用完成所有事情。信不信由你,习惯之后再回到其他方式编程是很难的。

我认为这是一个追加到列表前面的运算符?

是的,那就是(::)。您希望返回从两个输入列表的元素构建的列表,因此您希望使用(::)运算符构建列表。值得注意的是(::)只是一个函数。

此外,就匹配多个列表而言,当其中一个模式匹配之间存在逗号时(例如“h1 :: t1,h2 :: t2 - &gt;”,这是否意味着“两者仍然留有“,或”仍有剩余物品。“

这意味着两者仍有剩余物品。

另外,为什么h1 :: t1代表非空列表?这只是一个特例吗?我以为::是要附加到列表的前面?

正如Ben解释的那样,(::)用作运算符作为模式组件。当您在模式中使用它时,意味着您有一个头部和尾部的列表。尾巴可以是空的,但头部必须是东西。因此模式h1::t1仅匹配至少1长的列表。 h1匹配列表的头部(示例中为int),t1匹配列表的尾部(另一个列表缩短了1个元素)。

这不是真的特殊情况。在OCaml中定义自己的类型时,可以使用相同的名称获得这两个相同的东西:构造函数(用于构建类型的值)和析构函数(用来进行模式匹配)。但是,对于列表构造函数和析构函数使用运算符(::)有点特殊。列表通常在OCaml中得到一些特殊处理。但除了特殊的语法之外,它只是一种普通的类型。