Императивные конструкции: 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.