可能的OCaml代码生成错误

时间:2016-09-01 04:28:54

标签: conditional ocaml code-generation lint imperative-programming

以下自包含代码突出显示了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建议并注释掉其他分支。具有taureg模式的Emacs不会重新缩进此代码,除非明确要求我们相信一切都很好。

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

感谢。

2 个答案:

答案 0 :(得分: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/thenlet混合时必须非常小心。我有这样的问题。 if/thenelse控制单个表达式的一般直觉虽然为真,但当其中一个表达式为let时很容易出错。

答案 1 :(得分:1)

正如杰弗里所回答的那样,你的代码缩进中的可读性与实际解析代码的方式截然不同。

使用适当的自动缩进工具可以避免这种错误,例如caml-mode,tuareg-mode,ocp-indent和OCaml的vim插件。

通过自动缩进if的第二个init_graph,您可以立即发现它位于第一个if then条款下。