Возможная ошибка генерации кода OCaml

Следующий автономный код указывает на проблему в 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 помечает ветку else в init_graph2 как "бесполезную", что явно неверно.

Во-вторых, именно в этом случае метод отступов, предложенный camlspotter, может ввести в заблуждение. Мы следуем совету Ocamllint и закомментируем ветку else. Emacs с taureg-mode не меняет отступ в этом коде, если его явно не попросить, чтобы мы поверили, что все в порядке.

Что необходимо, так это инструмент, похожий на ворс, который выдает предупреждение в таких ситуациях. Жду хороших предложений по этому поводу.

Спасибо.


person user5754881    schedule 01.09.2016    source источник


Ответы (2)


Ваша проблема связана с обработкой 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, все анализируется так, как вы думаете.

Это правда, вы должны быть очень осторожны при смешивании if/then с let в OCaml. У меня были такие проблемы. Общее интуитивное представление о том, что if/then и else управляют одним выражением, хотя и верно, легко ошибиться, когда одно из выражений является let.

person Jeffrey Scofield    schedule 01.09.2016
comment
небольшое дополнение: замена строк let .. in и begin .. должна дать вам предполагаемую группировку - person user3240588; 01.09.2016
comment
Это объясняет это. Я использую taureg-mode, но все же пропустил отступ, так как он такой тонкий и неожиданный. Есть ли автоматический инструмент, такой как ocamllint, который выделяет их как потенциальные проблемные места? У меня есть похожий код в нескольких местах моей кодовой базы. Кроме того, может ли что-то подобное объяснить мой другой вопрос о том, что ocaml принимает обе ветки? Спасибо! - person user5754881; 01.09.2016

Как ответил Джеффри, ваше намерение, которое можно прочитать из отступов вашего кода, сильно отличается от того, как на самом деле анализируется код.

Вы можете избежать подобных ошибок, используя соответствующие инструменты автоматического отступа, такие как caml-mode, tuareg-mode, ocp-indent и плагины vim для OCaml.

Путем автоматического отступа второго if из init_graph вы сразу же обнаружите, что он находится под первым if пунктом then.

person camlspotter    schedule 01.09.2016