如何从OCaml中的for循环中获取值

ppa*_*l74 4 ocaml

我编写了以下代码,从列表中随机选择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)


Jef*_*eld 7

在我看来,你就像开始使用OCaml并拥有必要的编程背景.考虑OCaml(以及一般的函数式编程)的最佳建议可能是开始将所有程序结构都视为表达式,即带有值的东西.在命令式编程中,很多东西(比如for循环,while循环,if语句)没有值,只是"做"事情.在函数式编程中,一切都有价值.

由于OCaml不是函数式语言,因此保留一个值来表示只是"做"某事的表达式.值如下:().此值的类型称为unit(它是该类型的唯一值unit).

由于for循环只是为了"做"事情,即使在OCaml中,循环内的表达式也应该具有类型unit.但是你写的东西有列表类型.任何看起来都x :: y必须是列表,因为::构造函数创建一个列表.这就是编译器警告你的内容.循环内部应该有类型unit,但它有一个列表类型.

你的第二个问题是,表达x :: y没有改变的价值y.这是函数式编程的本质.所有表达式都是计算一个新值(列表).它不会更改表达式中出现的任何值.因此,acc最终会得到与之相同的值.

我真的建议你使用递归(如Basile Starynkevitch所建议的)来解决这个问题,而不是试图使用像for循环那样的命令式构造.它将导致更多惯用的OCaml代码.如果for由于某种原因确实需要使用循环,则需要使acc变量成为可变值,并且需要在循环中对其进行变异(更改).