我正在学习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中编码对我来说是一种令人沮丧的新体验。
答案 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 :: xs
将x
绑定到1
,y
绑定到[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)
,你可以在xxx
和yyy
部分中拥有你想要的任何函数调用(如果它们在类型上有意义的话)。这实际上是函数式编程的本质:它可以通过函数调用完成所有事情。信不信由你,习惯之后再回到其他方式编程是很难的。
我认为这是一个追加到列表前面的运算符?
是的,那就是(::)。您希望返回从两个输入列表的元素构建的列表,因此您希望使用(::)运算符构建列表。值得注意的是(::)只是一个函数。
此外,就匹配多个列表而言,当其中一个模式匹配之间存在逗号时(例如“h1 :: t1,h2 :: t2 - &gt;”,这是否意味着“两者仍然留有“,或”仍有剩余物品。“
这意味着两者仍有剩余物品。
另外,为什么h1 :: t1代表非空列表?这只是一个特例吗?我以为::是要附加到列表的前面?
正如Ben解释的那样,(::)用作运算符和作为模式组件。当您在模式中使用它时,意味着您有一个头部和尾部的列表。尾巴可以是空的,但头部必须是东西。因此模式h1::t1
仅匹配至少1长的列表。 h1
匹配列表的头部(示例中为int
),t1
匹配列表的尾部(另一个列表缩短了1个元素)。
这不是真的特殊情况。在OCaml中定义自己的类型时,可以使用相同的名称获得这两个相同的东西:构造函数(用于构建类型的值)和析构函数(用来进行模式匹配)。但是,对于列表构造函数和析构函数使用运算符(::)有点特殊。列表通常在OCaml中得到一些特殊处理。但除了特殊的语法之外,它只是一种普通的类型。