0

以下自包含代码突出显示了 OCaml 中的一个问题,可能与代码生成有关。数组 x 具有 [0..9] 中节点的连接信息。函数 init_graph 最初为每个节点构造了传入节点的显式数组。下面显示的简化版本仅打印两个连接的节点。

函数 init_graph2 与 init_graph 相同,除了一个“无用”的 else 分支。但是这两个函数产生的输出是完全不同的。您可以运行它并看到 init_graph 在某些情况下跳过了第二个 if-then-else!

我们已经在版本 3.12.1(适当替换了 make_matrix)、4.03.0 和 4.03.0+flambda 上运行了这个程序。他们都有同样的问题。

我一直在处理 OCaml 神秘地跳过分支或在某些情况下同时占用两个分支的这个问题和相关问题。感谢一位合作者,我们能够将真实代码缩减为一个小的自包含示例。

关于这里发生了什么的任何想法?有没有办法避免这个和相关的问题?

let x =
   let arr = Array.make_matrix 10 10 false in
     begin
      arr.( 6).( 4) <- true;
      arr.( 2).( 9) <- true;
     end;
     arr

let init_graph () =
   for i = 0 to 9 do
     for j = 0 to (i-1) do
       begin
         if x.(i).(j) then
           let (i_inarr, _) = ([||],[||]) in
           begin
             Format.printf "updateA: %d %d \n" i j;
           end
         (* else () *)
        ;
       if x.(j).(i) then
         let (j_inarr, _) = ([||],[||]) in
         begin
           Format.printf "updateB: %d %d \n" i j;
         end
       end
    done
 done;
 Format.printf "init_graph: num nodes is %i\n" 10

let init_graph2 () =
  for i = 0 to 9 do
    for j = 0 to (i-1) do
      begin
        if x.(i).(j) then
          let (i_inarr, _) = ([||],[||]) in
          begin
            Format.printf "updateA: %d %d \n" i j;
          end
        else ()
        ;
        if x.(j).(i) then
          let (j_inarr, _) = ([||],[||]) in
          begin
            Format.printf "updateB: %d %d \n" i j;
          end
        end
      done
   done;
   Format.printf "init_graph: num nodes is %i\n" 10

 let test1 = init_graph ()

 let test2 = init_graph2 ()

更新: Ocamllint 将 init_graph2 中的 else 分支标记为“无用”,这显然是错误的。

其次,camlspotter 建议的缩进方法在这种情况下可能会产生误导。我们遵循 Ocamllint 的建议并注释掉 else 分支。带有 taureg-mode 的 Emacs 不会重新缩进这段代码,除非明确要求我们相信一切都很好。

所需要的是一种类似 lint 的工具,可以在这些情况下发出警告。我正在等待这个的好建议。

谢谢。

4

2 回答 2

4

您的问题似乎与let...的处理有关in。此构造引入了一系列以分号分隔的表达式,而不是单个表达式。所以这段代码:

   if x.(i).(j) then
     let (i_inarr, _) = ([||],[||]) in
     begin
       Format.printf "updateA: %d %d \n" i j;
     end
   (* else () *)
   ;
   if x.(j).(i) then
     let (j_inarr, _) = ([||],[||]) in
     begin
       Format.printf "updateB: %d %d \n" i j;
     end

实际上是这样解析的:

     if x.(i).(j) then
       let (i_inarr, _) = ([||],[||]) in
       begin
         Format.printf "updateA: %d %d \n" i j;
       end
           (* else () *)
       ;
       if x.(j).(i) then
         let (j_inarr, _) = ([||],[||]) in
         begin
           Format.printf "updateB: %d %d \n" i j;
         end

换句话说,第一个begin/end和第二个if/then都由第一个控制if/then

另一种说法是;优先级高于let ... in. 所以let x = y in a ; b被解析为let x = y in (a; b),而不是(let x = y in a); b

当您包含“无用”else时,事情会像您认为的那样解析。

确实,在 OCaml 中混合if/then时必须非常小心。let我遇到过这样的问题。if/then和控制单个表达式的一般直觉else虽然是正确的,但当其中一个表达式是 a 时很容易出错let

于 2016-09-01T04:58:38.903 回答
1

正如 Jeffrey 所回答的那样,您可以从代码缩进中读取的意图与实际解析代码的方式非常不同。

您可以通过使用适当的自动缩进工具来避免此类错误,例如用于 OCaml 的 caml-mode、tuareg-mode、ocp-indent 和 vim 插件。

通过自动缩进 中的第二个ifinit_graph您可以立即发现它在第一个if子句下then

于 2016-09-01T05:13:43.120 回答