Императивные конструкции: for, while, побочные эффекты

Осваиваем императивную сторону OCaml — циклы и последовательное выполнение действий — и учимся выбирать стиль осознанно.

В OCaml есть полноценные циклы for и while, но они работают только с действиями типа unit — то есть с побочными эффектами, а не с вычислением значений.

Хотя идиоматичный OCaml тяготеет к рекурсии и свёрткам, язык не запрещает циклы. Иногда императивный проход по массиву читается яснее и работает быстрее. Прагматизм OCaml в том, чтобы дать выбор, а не навязать догму.

Цикл for

let () =
  for i = 1 to 5 do
    Printf.printf "%d " i
  done;
  print_newline ()

Вывод:

1 2 3 4 5 

Есть обратный вариант for i = 5 downto 1 do ... done. Переменная i неизменяема внутри тела. Цикл всегда возвращает ().

Цикл while

let () =
  let i = ref 1 in
  let sum = ref 0 in
  while !i <= 100 do
    sum := !sum + !i;
    incr i           (* то же, что i := !i + 1 *)
  done;
  Printf.printf "Сумма: %d\n" !sum

Вывод:

Сумма: 5050

Функции incr и decr — сокращения для увеличения/уменьшения int ref на единицу.

Последовательность действий

let log_and_add x y =
  Printf.printf "складываю %d и %d\n" x y;
  x + y      (* последнее выражение — результат *)

Несколько действий подряд разделяют точкой с запятой ;. Все, кроме последнего, должны иметь тип unit, иначе компилятор предупредит (Warning 10), что значение отбрасывается.

Когда императивный стиль уместен

  • Проход по массиву с обновлениемfor с a.(i) <- ... естественнее рекурсии.
  • Ввод-вывод — печать, чтение строк по своей природе императивны.
  • Локальная оптимизация — горячий цикл проще написать императивно, спрятав за чистым интерфейсом.

А для преобразования данных выразительнее map/filter/fold — выбирайте инструмент под задачу.

Как работает под капотом

Циклы for и while компилируются в обычные машинные циклы — никакой рекурсии под капотом. Ограничение «тело типа unit» — следствие того, что цикл сам по себе выражение типа unit. Точка с запятой ; вычисляет левое выражение, отбрасывает его (требуя unit, чтобы вы не теряли результат) и переходит к правому. Это аккуратная защита: язык напоминает, когда вы выбрасываете полезное значение.

Частые ошибки

  • Ждать, что for вернёт значение. Цикл всегда unit; для результата используйте ref/массив или fold.
  • Присвоить переменной цикла i. Она неизменяема; для счётчика заводите ref.
  • Игнорировать Warning 10. Часто означает реальную потерю значения.

Итоги

  • for и while существуют, но их тело и сам цикл имеют тип unit — это про эффекты, не про значения.
  • Действия разделяются ;; все, кроме последнего, должны быть unit.
  • Императив уместен для массивов, ввода-вывода и локальных оптимизаций; для данных предпочтительны map/fold.
Проверьте себя
1. Какой тип имеет тело цикла for в OCaml?
Aint
Bunit
Cbool
Dлюбой
2. Зачем циклу while обычно нужна ref-переменная?
AДля скорости
BЧтобы условие могло меняться между итерациями — обычные let неизменяемы
CЭто требование синтаксиса
DЧтобы вернуть значение
3. Что означает Warning 10 при использовании `;`?
AСинтаксическая ошибка
BЛевое выражение перед ; имеет не-unit тип — значение, возможно, теряется по ошибке
CЦикл бесконечен
DТип не выведен