可能的 OCaml 代码生成错误
Possible OCaml code generation bug
以下自包含代码突出了 OCaml 中的一个问题,可能与代码生成有关。
数组 x 具有 [0..9] 中节点的连接信息。函数 init_graph 最初为每个节点构造了传入节点的显式数组。下面显示的简化版本仅打印两个连接的节点。
函数 init_graph2 与 init_graph 相同,除了 "useless" 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 分支标记为 "useless",这显然是错误的。
其次,在这种情况下,camlspotter 建议的缩进方法可能会产生误导。我们遵循 Ocamllint 的建议并注释掉 else 分支。具有 taureg 模式的 Emacs 不会重新缩进此代码,除非明确要求让我们相信一切都很好。
我们需要的是一种类似 lint 的工具,可以在这些情况下发出警告。我正在等待这方面的好建议。
谢谢。
您的问题似乎与 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
.
当您包含 "useless" else
时,事情会按照您认为应该的方式进行解析。
确实如此,在 OCaml 中将 if/then
与 let
混合使用时必须非常小心。我遇到过这样的问题。 if/then
和 else
控制单个表达式的一般直觉虽然正确,但当其中一个表达式是 let
.
时很容易出错
正如 Jeffrey 所回答的,从代码缩进中可读的意图与代码的实际解析方式大不相同。
您可以通过使用适当的自动缩进工具来避免此类错误,例如 caml-mode、tuareg-mode、ocp-indent 和 OCaml 的 vim 插件。
通过自动缩进 init_graph
的第二个 if
,您可以立即发现它在第一个 if
的 then
子句下。
以下自包含代码突出了 OCaml 中的一个问题,可能与代码生成有关。 数组 x 具有 [0..9] 中节点的连接信息。函数 init_graph 最初为每个节点构造了传入节点的显式数组。下面显示的简化版本仅打印两个连接的节点。
函数 init_graph2 与 init_graph 相同,除了 "useless" 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 分支标记为 "useless",这显然是错误的。
其次,在这种情况下,camlspotter 建议的缩进方法可能会产生误导。我们遵循 Ocamllint 的建议并注释掉 else 分支。具有 taureg 模式的 Emacs 不会重新缩进此代码,除非明确要求让我们相信一切都很好。
我们需要的是一种类似 lint 的工具,可以在这些情况下发出警告。我正在等待这方面的好建议。
谢谢。
您的问题似乎与 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
.
当您包含 "useless" else
时,事情会按照您认为应该的方式进行解析。
确实如此,在 OCaml 中将 if/then
与 let
混合使用时必须非常小心。我遇到过这样的问题。 if/then
和 else
控制单个表达式的一般直觉虽然正确,但当其中一个表达式是 let
.
正如 Jeffrey 所回答的,从代码缩进中可读的意图与代码的实际解析方式大不相同。
您可以通过使用适当的自动缩进工具来避免此类错误,例如 caml-mode、tuareg-mode、ocp-indent 和 OCaml 的 vim 插件。
通过自动缩进 init_graph
的第二个 if
,您可以立即发现它在第一个 if
的 then
子句下。