我编写了以下代码,从列表中随机选择n个项目,并将选择放在新列表中.代码如下
let random_extract list n =
let l = ((List.length list)- 1)
and acc = [] in
for i = 1 to n do
(List.nth list (Random.int l) ) :: acc (* Line 447 *)
done;
acc
;;
Run Code Online (Sandbox Code Playgroud)
当我加载包含此代码的文件时,我收到以下错误
File "all_code.ml", line 447, characters 2-40:
Warning 10: this expression should have type unit.
val random_extract : 'a list -> int -> 'b list = <fun>
Run Code Online (Sandbox Code Playgroud)
两个问题,问题1:这个警告意味着什么.
其次,当我运行这个函数时,我没有得到预期的列表,但是一个空列表.
问题2:如何从for循环中获取acc的值
And*_*uer 10
杰弗里斯科菲尔德所说的话,还有:
let random_extract list n =
let l = ((List.length list)- 1)
and acc = ref [] in
for i = 1 to n do
acc := (List.nth list (Random.int l) ) :: !acc (* Line 447 *)
done;
!acc
Run Code Online (Sandbox Code Playgroud)
如果你要使用for循环来做类似的东西,那么acc必须是一个引用,即一个可变的值.在Ocaml和ML中通常let x = ...定义一个不可变的值.接下来,当你写作时(List.nth list (Random.int l)) :: acc,这只是意味着"看,我可以制作一个清单".你没有说新构造的列表必须分配给任何东西,特别是它没有分配给acc它(它不能,因为它acc是不可变的).Ocaml发出警告是因为它看到你的for循环体产生了一个列表,但Ocaml知道for循环体应该产生一个单元(C/C++/Java错误术语中的"void").
使用这样的for循环很难看.正确的方法是这样做:
let random_extract lst =
let l = List.length lst - 1 in
let rec extract = function
| 0 -> []
| n -> List.nth lst (Random.int l) :: extract (n - 1)
in
extract
Run Code Online (Sandbox Code Playgroud)
如果您是Ocaml的新手,您真的应该花时间完全理解上述代码.之后,您应该学习以下代码,并确保您理解为什么第一个版本更有效(查找"尾递归"):
let random_extract lst =
let l = List.length lst - 1 in
let rec extract acc = function
| 0 -> acc
| n -> extract (List.nth lst (Random.int l) :: acc) (n - 1)
in
extract []
Run Code Online (Sandbox Code Playgroud)
在我看来,你就像开始使用OCaml并拥有必要的编程背景.考虑OCaml(以及一般的函数式编程)的最佳建议可能是开始将所有程序结构都视为表达式,即带有值的东西.在命令式编程中,很多东西(比如for循环,while循环,if语句)没有值,只是"做"事情.在函数式编程中,一切都有价值.
由于OCaml不是纯函数式语言,因此保留一个值来表示只是"做"某事的表达式.值如下:().此值的类型称为unit(它是该类型的唯一值unit).
由于for循环只是为了"做"事情,即使在OCaml中,循环内的表达式也应该具有类型unit.但是你写的东西有列表类型.任何看起来都x :: y必须是列表,因为::构造函数创建一个列表.这就是编译器警告你的内容.循环内部应该有类型unit,但它有一个列表类型.
你的第二个问题是,表达x :: y并没有改变的价值y.这是函数式编程的本质.所有表达式都是计算一个新值(列表).它不会更改表达式中出现的任何值.因此,acc最终会得到与之相同的值.
我真的建议你使用递归(如Basile Starynkevitch所建议的)来解决这个问题,而不是试图使用像for循环那样的命令式构造.它将导致更多惯用的OCaml代码.如果for由于某种原因确实需要使用循环,则需要使acc变量成为可变值,并且需要在循环中对其进行变异(更改).