0

次の自己完結型コードは、おそらくコード生成に関する OCaml の問題を強調しています。配列 x には、[0..9] 内のノードの接続情報があります。関数 init_graph は元々、すべてのノードの着信ノードの明示的な配列を構築していました。以下に示す縮小版は、接続された 2 つのノードを出力するだけです。

関数 init_graph2 は、「役に立たない」else ブランチを除いて、init_graph と同じです。しかし、これら 2 つの関数によって生成される出力はまったく異なります。実行すると、init_graph が 2 番目の 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と 2 番目の両方が最初のif/thenによって制御されif/thenます。

別の言い方をすれば、 は;よりも優先順位が高いということですlet ... in。Soは asではなく として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と が 1 つの式を制御するという一般的な直感はelse、真ですが、式の 1 つが である場合、簡単に誤解されますlet

于 2016-09-01T04:58:38.903 に答える
1

ジェフリーが答えたように、コードのインデントから読み取れる意図は、コードが実際に解析される方法とは大きく異なります。

Caml-mode、tuareg-mode、ocp-indent、OCaml 用の vim プラグインなどの適切な自動インデント ツールを使用することで、この種の間違いを回避できます。

ifの 2 番目を自動インデントすると、最初の の節のinit_graph下にあることがすぐにわかります。ifthen

于 2016-09-01T05:13:43.120 に答える