Всё — выражение: let и значения
Понимаем фундаментальную идею: в OCaml почти любая конструкция — это выражение, у которого есть значение и тип.
Выражение — фрагмент кода, который вычисляется в значение определённого типа; в OCaml выражениями являются даже
ifиmatch.
В императивных языках есть чёткое деление на инструкции (что-то делают) и выражения (вычисляют значение). В OCaml эта граница почти стёрта: if, match, блок let ... in — всё это выражения, у каждого есть значение и тип. Это практическое удобство: вы можете подставить любое из них туда, где ожидается значение.
let связывает имя со значением
Ключевое слово let вводит связывание (binding) — даёт имени значение. Важно: это не «переменная»-ячейка, которую можно перезаписать. Это неизменяемое связывание: x навсегда означает то значение, с которым его связали.
let x = 10
let y = x * 2
let message = "Результат: " ^ string_of_int y
Здесь ^ — конкатенация строк, а string_of_int превращает число в строку. Каждое let на верхнем уровне модуля создаёт значение, видимое ниже по файлу.
Локальное let ... in
Внутри выражения связывание оформляется как let имя = значение in тело. Имя видно только в тело:
let area =
let pi = 3.14159 in
let r = 5.0 in
pi *. r *. r
Здесь pi и r существуют только внутри вычисления area. Оператор *. — это умножение для чисел с плавающей точкой. Всё выражение let ... in ... само имеет значение — то, в которое вычисляется тело после in.
if — это тоже выражение
Поскольку if — выражение, обе ветки обязаны иметь один тип, и результат можно сразу присвоить:
let sign n =
if n > 0 then "положительное"
else if n < 0 then "отрицательное"
else "ноль"
Здесь if сам по себе и есть значение функции sign. Если опустить ветку else у выражения, тип которого не unit, компилятор выдаст ошибку — иначе у выражения не было бы значения в одном из случаев.
Как работает под капотом
Превращение всего в выражения — следствие происхождения OCaml из лямбда-исчисления, где программа — это вычисление выражения. Компилятор представляет код как дерево выражений, у каждого узла которого есть тип. Именно поэтому вывод типов работает так гладко: компилятор обходит дерево и собирает уравнения на типы. Неизменяемость связываний let упрощает рассуждения: вы знаете, что имя не «протухнет» дальше в коде.
Частые ошибки
- Думать, что
let x = 5— присваивание. Это связывание; перезаписатьxнельзя. Для изменяемости естьref. - Забыть
inв локальном связывании внутри функции — синтаксическая ошибка. - Считать
=присваиванием. В OCaml=— сравнение значений; присваивания в обычном смысле тут нет.
Итоги
- В OCaml почти всё — выражение со значением и типом:
if,match,let ... in. let имя = значениесоздаёт неизменяемое связывание, а не перезаписываемую переменную.- Локальное
let ... inограничивает видимость имени телом послеin.