# Глава 1. Описание языка

В этой части содержится учебное введение в язык Objective Caml. Подразумевается, что читатель хорошо владеет навыками программирования на императивных языках (типа Pascal или C), однако знание функциональных языков не требуется. В главе 3 описываются объектно-ориентированные возможности, а в главе 2 - система модулей.

## 1.1 Основы

Для обзора Caml используется интерактивная система, которая запускается командой `ocaml` из командной оболочки Unix или приложением `Ocamlwin.exe` в среде Windows. Все введение представляет собой журнал одной сессии.

Интерактивная система выводит приглашение `#`, ожидает от пользователя ввода фраз Caml, которые тот заканчивает символами `;;`, затем компилирует их на лету, выполняeт и выводит результат. Фразы могут быть простыми выражениями или определениями идентификаторов (переменных или функций) `let`.

    # 1+2*3;;

    - : int = 7

    # let pi = 4.0 *. atan 1.0;;

    val pi : float = 3.14159265359

    # let square x = x *. x;;

    #val square : float -> float = <fun>

    # square(sin pi) +. square(cos pi);;

    - : float = 1.

Система Caml вычисляет как значение, так и тип фразы. Даже в случае с аргументами функции явное указание типа не требуется. Оно выводится из того, как переменные используются внутри функции. Следует, однако, обратить внимание, что целые числа и числа с плавающей точкой являются разными типами с разными операторами: + и * предназначены для целых, а +. и *. для чисел с плавающей точкой.

    # 1.0 * 2;;

    This expression has type float but is here used with type int

Рекурсивные функции определяются конструкцией `let rec`.

    # let rec fib n = if n < 2 then 1 else fib(n - 1) + fib(n -2);;

    val fib: int ->; = <fun>

    # #       fib 10;;

    -: int = 89

## 1.2 Типы данных

Помимо целых чисел и чисел с плавающей точкой Caml поддерживает обычные типы данных - булевы значения, символы и символьные строки.

    # (1 < 2) = false;;

    - : bool = false

    # 'a';;

    - : char = 'a'

    # "Hello world";;

    - : string = "Hello world"

Кроме того, к предопределенным типам относятся кортеж, список и массив. Механизм добавления новых типов будет рассмотрен позднее, а сейчас речь пойдет о списках. Списки задаются либо как перечень значений, разделенных точкой с запятой, в квадратных скобках, либо строятся из пустого списка `[]` (он же "nil"), в начало которого с помощью оператора `::` (или "cons") добавляются элементы.

    # let l = ["is"; "a"; "tale"; "told"; "etc."];;

    val l : string list = ["is"; "a"; "tale"; "told"; "etc."]

    # "Life" :: l;;

    - :  string list = ["Life"; "is"; "a"; "tale"; "told"; "etc."]

Списки, как и другие структуры данных, не надо явно размещать и уничтожать в памяти: в Caml управление памятью полностью автоматическое. Аналогично, явного управления указателями нет - компилятор Caml сам при необходимости создает указатели.

Исследование и изменение структуры списков, как и большинства других структур данных осуществляется путем сопоставления с образцом (pattern-matching). Образец записывается в той же форме, что и определение списка, а идентификатор представляет неизвестную часть списка. Вот пример сортировки списка вставкой:

    #let rec sort lst =
       match lst with
         [] -> []
       | head :: tail -> insert head (sort tail)
     and insert elt lst =
       match lst with
         [] -> [elt]
       | head :: tail -> if elt <= head then elt :: lst else head :: insert elt tail
     ;;

    val sort: 'a list -> 'a list = <fun>
    val insert 'a -> 'a list -> 'a list = <fun>

    # sort l;;

    -: string list = ["a"; "etc."; "is"; "tale"; "told"]

Тип данных, найденный для функции `sort`(`'a list -> 'a list`), означает, что она может работать со списками, состоящими из данных любого типа и возвращать такие же списки. Тип `'a` называется переменной типа и может соответствовать любому типу данных. Функция `sort` будет работать со списками, состоящими из любых типов, поскольку операции сравнения в Caml (=, <= и т.д.) полиморфны: они работают с любыми переменными одного типа. Таким образом, и функция `sort` становится полиморфной для списков любых типов.

    # sort [6;2;5;3;];;

    - : int list = [2; 3; 5; 6]

    # sort [3.14; 2.718];;

    - : float list = [2.718; 3.14]

Функция `sort` не изменяет исходный список. Она строит и возвращает новый список, состоящий из тех же элементов, расположенных в порядке возрастания. В Caml нет возможности изменить уже созданный список, поэтому он является неизменяемым (immutable). То же самое относится к большинству структур данных, однако существуют и изменяемые (mutable) типы (в первую очередь, массивы).

## 1.3 Функции как значения

Caml - функциональный язык. Он поддерживает функции в математическом смысле, так что последние могут обрабатываться как обычные данные. Например, функция `deriv` принимает в качестве аргумента любую функцию, оперирующую числами с плавающей точкой, и возвращает ее производную:

    # let deriv f dx = function x -> (f (x +. dx) -. f(x)) /. dx;;

    val deriv: (float -> float) -> float -> float -> float = <fun>

    # let sin' = deriv sin 1e-6;;

    val sin' : float -> float = <fun>

    # sin' pi;;

    -: float = -1.00000000014

Можно определять даже составные функции:

    # let compose f g = function x -> f(g(x));;

    val compose: ('a -> 'b) -> ('c ->>
              'a) -> 'c -> 'b = <fun>

    # let cos2 = compose square cos;;

    val cos2: float -> float = <fun>

Функции, принимающие в качестве аргумента другие функции, называются "функционалами" или "функциями высшего порядка". Они особенно удобны, когда возникает потребность в итераторе или подобной ему операции для структуры данных. Стандартная библиотека Caml включает фукнкционал `List.map`, применяющий функцию к каждому элементу списка и возвращающий список, составленный из результатов функции.

    # List.map(function n -> n*2 + 1) [0;1;2;3;4];;

    - : int list = [1; 3; 5; 7; 9]

Этот функционал, как и некоторые другие функционалы для массивов и списков, предопределен, однако в нем нет ничего необычного. Он может быть записан следующим образом:

    # let rec map f l =
        match l with       [] -> []     | hd :: tl -> f hd :: map f tl;;

    val map: ('a -> 'b) -> 'a list 'b list = <fun>

## 1.4 Записи и варианты

Пользователь может определять свои типы данных с помощью записей и вариантов. Для этого используется декларация `type`. Ниже приведено определение типа рационального числа в виде записи.

    # type ratio = {num: int; denum: int};;

    type ratio =  {num: int; denum: int};;

    # let add_ratio r1 r2 =
       {num = r1.num * r2.denum + r2.num * r1.denum;   denum = r1.denum * r2.denum};;

    val add_ratio : ratio -> ratio -> ratio = <fun>

    # add_ratio {num = 1; denum =3} {num = 2; denum =5};;

    - : ratio = {num=11; denum=15}

В определении вариантного типа перечисляются все допустимые формы его значения. Каждая задается по имени, которое называется конструктором и служит как для создания значений вариантного типа, так и для исследования путем сравнения с образцом. Конструкторы записываются с заглавной буквы - так их можно отличить от имен переменных (последние должны начинаться со строчной). Вот, например, вариантный тип для смешанной арифметики, допускающей операции с целыми и числами с плавающей точкой:

    # type number = Int of int | Float of float | Error;;

    type number = Int of int | Float of float | Error

Значение типа `number` может быть целым, числом с плавающей точкой, или константой `Error`, соответствующей результату недопустимой операции (например, деления на ноль).

Особым случаем вариантных типов являются перечисляемые типы. Все альтернативы в них - константы.

    # type sign = Positive | Negative;;

    type sign = Positive | Negative

    # let sign_int n = if n => 0 then Positive else Negative;;

    val sign_int: int -> sign = <fun>

При определении арифметических функций с типом `number` используется сопоставление с образцом обоих чисел, участвующих в операции.

    # let add_num n1 n2 =
       match (n1, n2) with
          (Int i1, Int i2) ->
             (* Проверка на переполнение при сложении целых *)
             if sign_int i1 = sign_int i2 && sign_int(i1 + i2) <> sign_int i1
             then Float(float i1 +. float i2)
             else Int(i1 + i2)
       | (Int i1, Float f2) -> Float(float i1 +. f2)
       | (Float f1, Int i2) -> Float(f1 +. float i2)
       | (Float f1, Float f2) -> Float(f1 +. f2)
       | (Error, _) -> Error
       | (_, Error) -> Error;;

    val add_num: number -> number -> number = <fun>

    # add_num Int(123) Float(3.14159);;

    -: number Float 126.14159

Чаще всего вариантные типы используются для описания рекурсивных структур данных. Вот, например, бинарное дерево:

    # type 'a btree = Empty | Node of 'a * 'a btree * 'a btree;;

    type 'a btree = Empty | Node of 'a * 'a btree * 'a btree

Это определение читается следующим образом: бинарное дерево, содержащее значение типа `'a` (то есть, произвольное) либо пусто, либо является узлом, содержащим одно значение типа `'a` и два поддерева, каждое из которых также содержит значение типа `'a`, точнее `'a btree`.

Операции с бинарными деревьями записываются в виде рекурсивных функций той же структуры, что и определение типа. Вот, например, операции поиска и вставки в упорядоченное бинарное дерево (элементы возрастают слева направо):

    # let rec member x btree =
       match btree with
          Empty -> false
          | Node(y, left, right) ->
             if x = y then true else
             if x < y then member x left else member x right;;

    val member : 'a -> 'a btree -> bool = <fun>

    # let insert x btree =
       match btree with
          Empty -> Node (x, Empty, Empty)
          | Node(y, left, right) ->
             if x <= y then Node(y, insert x left, right)
             else Node(y, left, insert x right);;

    val insert : 'a -> 'a btree -> 'a btree = <fun>

## 1.5 Императивные возможности

До сих пор все примеры были написаны исключительно в аппликативном стиле, но Caml включает и полный набор императивных возможностей - циклы `for` и `while`, а также изменяемые структуры данных, в частности, массивы. Массивы задаются либо как список в квадратных скобках `[|` и `|]`, либо заполняются после вызова функции `Array.create`. Приведенная ниже функция поэлементно складывает два вектора, представленных в виде массивов чисел с плавающей точкой.

    # let add_vect v1 v2 =
        let len = min (Array.length v1) (Array.length v2) in
        let res = Array.create len 0.0 in
        for i = 0 to len - 1 do
          res.(i) <- v1.(i) + v2.(1)
       done;   res;;

    val add_vect : float_array -> float_array -> float_array = <fun>

    # add_vect [| 1.0; 2.0 |] [| 3.0; 4.0|];;

    - : float_array[| 4.0; 6.0 |]

Записи также могут быть изменены оператором присваивания, если при их определении использовалось ключевое слово `mutable`.

    # type mutable_point = { mutable x : float; mutable y : float };;

    type mutable_point = { mutable x : float; mutable y : float }

    # let translate p dx dy =
       p.x <- p.x +. dx; p.y <- p.y +. dy;;

    val translate : mutable_point -> float -> float -> unit = <fun>

    # let mypoint = { x = 0.0; y = 0.0 };;

    val mypoint : mutable_point =  { x = 0.  ; y = 0. }

    # translate mypoint 1.0 2.0;;

    - : unit = ()

    # mypoint;;

    -  : mypoint = { x = 1. ; y = 2. }

В Caml отсутвует понятие переменной, как идентификатора, текущее значение которого может быть изменено операцией присваивания. (`let` на самом деле просто создает идентификатор с новой областью видимости.) Тем не менее стандартная библиотека включает средства для работы со ссылками, то есть изменяемыми областами памяти (или массивами из одного элемента). Оператор `!` используется для получения текущего значения ссылки, а оператор `:=` - для изменения. Вот пример сортировки вставкой, изменяющий массив:

    # let insertion_sort a =
       for i = 1 to Array.length a - 1 do
          let val_i = a.(i) in
          let j = ref i in
          while !j > 0 && val_i < a.(!j - 1) do
             a.(!j) <- a.(!j - 1)
             j := j - 1
          done;
          a.(!j) <- val_i
    done;;

    val insertion_sort : 'a array = <fun>

Другое применение ссылки находят в функциях, сохраняющих состояние между вызовами. Например, генератор псевдослучайных чисел ниже запоминает последнее возвращенное значение:

    # let current_rand = ref 0;;

    val current_rand : int ref = { contents = 0 }

    # let random () =
       current_rand := !current_rand * 25713 + 1345;
       !current_rand;;

    val random : unit -> int = <fun>

Как уже говорилось, в ссылках нет ничего необычного - они реализованы как одноэлементые массивы:

    # type 'a ref = { mutable contents : 'a };;

    type 'a ref = { mutable contents : 'a; }

    # let (!) r = r.contents;;

    val ( ! ) : 'a ref -> 'a = <fun>

    # let (:=) r newval = r.contents <- newval;;

    val ( := ) : 'a ref -> 'a -> unit = <fun>

В некоторых случаях требует поместить полиморфную функцию в структуру данных, сохранив ее полиморфность. Без пользовательского описания типа данных это невозможно, так как полиморфизм существует только на глобальном уровне. Однако поля структуры можно явно описать как полиморфные.

    # type idref = {mutable id: 'a. 'a -> 'a};;

    type idref = { mutable id: 'a. 'a -> 'a; }

    # let r = { id = fun x -> x };;

    val r : idref = { id = <fun> }

    # let g s = (s.id 1, s.id true);;

    val g : idref -> int * bool = <fun>

    # r.id <- (fun x -> print_string "called id\n"; x);;

    - : unit = ()

    # g r;;

           called id
    called id
    - : (int * bool) = (1, true)

## 1.6 Исключения

Исключения используются в Ocaml для оповещения об исключительных ситуациях и их обработки. Кроме того, они могут применяться как нелокальные контрольные структуры общего назначения. Исключения объявляются блоком `exception` и возбуждаются оператором `raise`. Функция ниже возвращает первый элемент списка и возбуждает исключение, если получает в качестве параметра пустой список.

    # exception Empty_list;;

    exception Empty_list

    # let head l =
       match l with
          [] -> raise Empty_list
       hd :: tl -> hd;;

    val head : 'a list -> 'a = <fun>

    # head [1;2];;

    - : int = 1

    # head [];;

    Exception: Empty_list

В стандартной библиотеке исключения сообщают о ситуациях, когда функции не могут завершить работу в обычном порядке. Например, функция `List.accoc`, возвращающая значение, ассоциированное с ключом в списке пар ключ-значение, возбуждает предопределенное исключение `Not_found`, если заданного ключа в списке нет.

    # List.assoc 1 [(0, "zero"); (1, "one")];;

    - : string = "one"

    # List.assoc 2 [(0, "zero"); (1, "one")];;

                Exception: Not_found

Исключение перехватываются в блоке `try... with`:

    # let name_of_binary_digit =
       try
          List_assoc digit [(0, "zero"); (1, "one")]
       with Not_found ->
          "not a binary digit";;

    val name_of_binary_digit : int -> string = <fun>

    # name_of_binary_digit 0;;

    - : string = "zero"

    # name_of_binary_digit (-1);;

    - : string = "not a binary digit"

В ветви `with` на самом деле выполняется сравнение с образцом. Таким образом, в блоке `try... with` можно перехватить несколько исключений. Финализация же может выглядеть как перехват всех исключений, выполнение некоторых завершающих действий и повторный выброс исключения.

    # let temporarily_set_reference ref newval funct =
       let oldval = !ref in
       try
          ref := newval;
          let res = funct() in
          ref := oldval;
          res
       with x ->
          let ref := oldval;
          raise x;;

    val temporarily_set_reference : 'a -> 'a -> (unit -> 'b) -> 'b = <fun>

## 1.7 Символическая обработка выражений

Введение завершается более полным примером, демонстрирующим применение Caml для символической обработки: формальной манипуляции арифметическими выражениями с переменными. Следующий вариантный тип описывает допустимые выражения:

    # type expression =
            Const of Float
            | Var of String
            | Sum of expression * expression (* e1 + e2 *)
            | Diff of expression * expression (* e1 - e2 *)
            | Prod of expression * expression   (* e1 * e2 *)
            | Quot of expression * expression   (* e1 / e2 *)
            ;;

    # type expression =
            Const of float
            | Var of string
            | Sum of expression * expression
            | Diff of expression * expression
            | Prod of expression * expression
            | Quot of expression * expression

Для начала определим функцию, которая будет вычислять выражение по заданным аргументам (они будут передаваться в виде пар имена-значения). Для простоты аргументы оформляются в виде ассоциативного списка.

    # exception Unbound_variable of string;;

    exception Unbound_variable of string

    # let rec eval env exp =
            match exp with
             Const c -> c
             | Var v ->
             (try List.assoc v env with Not_found -> raise(Unbound_variable v))
             | Sum(f, g) -> eval env f +. env g
             | Diff(f, g) -> eval env f -. env g
             | Prod(f, g) -> eval env f *. env g
             | Quot(f, g) -> eval env f /. env g;;

    var eval : (string * float) list -> expression -> float = <fun>

    # eval [("x", 1.0); ("y", 3.14)] (Prod(Sum(Var "x", Const 2.0), Var "y"));;

    - : float = 9.42

Непосредственно для символической обработки служит производная выражения по модулю `dv`.

    # let rec deriv exp dv =
              match exp with
              Const c -> Const 0.0
              | Var v -> if v = dv then Const 1.0 else Const 0.0
              | Sum(f, g) -> Sum(deriv f dv, deriv g dv)
              | Diff(f, g) -> Diff(deriv f dv, deriv g dv)
              | Prod(f, g) -> Sum(Prod(f, deriv g dv), Prod(deriv f dv, g))
              | Quot(f, g) -> Quot(Diff(Prod(deriv f dv, g), Prod(f, deriv g dv)),
              Prod(g, g));;

    val deriv : expression -> string -> expression = <fun>

    # deriv (Quot(Const "1.0", Var "x")) "x";;

    - : expression =
       Quot(Diff(Prod(Const 0., Var "x"),  Prod (Const 1., Const 1.)),
          Prod (Var "x", Var "x"))

## 1.8 Вывод и лексический разбор

По предыдущим примерам видно, что внутреннее представление (или абстрактный синтаксис) выражений становится трудным как для чтения, так и для написания по мере роста этих выражений. Нам нужны функции вывода и лексического разбора, чтобы переключаться между абстракным и конкретным синтаксисом (то есть, привычной алгебраической нотацией вида `2 * x + 1`.

Для функции вывода стоит воспользоваться стандартными правилами приоритета операторов (то есть, `*` выполяется раньше `+`), что позволит избежать вывода ненужных скобок. Поэтому скобки будут печаться лишь в том случае, когда текущий оператор должен выполняться с особым приоритетом.

    # let print_exp exp =
              (* Определение локальных функций *)
              let open_paren prec op_prec =
                if prec > op_prec then print_string "(" in
              let close_paren prec op_prec =
                if prec > op_prec then print_string ")" in
              let rec print prec exp =     (* prec -- приоритет текущего оператора *)
                match exp with
                  Const c -> print_float c
                  | Var v -> print_string v
                  | Sum(f, g) ->
                      open_paren prec 0;
                      print 0 f; print_string " + "; print 0 g;
                      close_paren prec 0
                  | Diff(f, g) ->
                      open_paren prec 0;
                      print 0 f; print_string " - "; print 1 g;
                      close_paren prec 0
                  | Prod(f, g) ->
                      open_paren prec 2;
                      print 2 f; print_string " * "; print 2 g;
                      close_paren prec 2
                  | Quot(f, g) ->
                      open_paren prec 2;
                      print 2 f; print_string " / "; print 3 g;
                      close_paren prec 2
              in print 0 exp;;

    val print_expr : expression -> unit = <fun>

    # let e = Sum(Prod(Const 2.0, Var "x"), Const 1.0);;

    val e : expression = Sum (Prod (Const 2., Var "x"), Const 1.)

    # print_expr e; print_newline();;

    2. * x + 1.
    - : unit = ()

    # print_expr (deriv e "x"); print_newline();;

    2. * 1. + 0. * x + 0.
    - : unit = ()

Лексический разбор (преобразование конкретного синтаксиса в абстрактный), как правило, сложнее. Caml включает несколько среств, облегчающих написание анализаторов: с одной стороны, есть Caml-версии лексического анализатора Lex и синтаксического анализатора YACC (см. главу 12), обрабатыващие LALR-языки с помощью автоматов с магазинной памятью, с другой - предопределенные типы потоков (символов и токенов) и операции сопоставления с образцом для потоков, облегчающие написание рекурсивных нисходящих анализаторов для LL-языков. Мы воспользуемся поточными анализаторами. Синтаксическая поддержка для них предусмотрена препроцессором Camlp4, который загружается в интерактивную среду директивой `#load`.

    ##load camlp4o.cma;;

       Camlp4 Parsing version 3.10+rc1

    # open Genlex;;

    # let lexer = make_lexer ["("; ")"; "+"; "-"; "*"; "/"];;

    val lexer : char Stream.t -> Genlex.token Stream.t = <fun>

Для фазы лексического разбора (трансформации текста в поток токенов) используется "обобщенный" лексический анализатор из модуля стандартной библиотеки `Generic`. Функция `make_lexer` принимает список аргументов и возвращает функцию лексического разбора, которая "токенизирует" поток символов. Токены могут быть идентификаторами, ключевыми словами или литералами (целыми, числами с плавающей точкой, символами, строками). Комментарии и пробелы пропускаются.

    # let token_stream = lexer(Stream.of_string "1.0 +x");;

    val token_stream : Genlex.token Stream.t = <abstr>

    # Stream.next token_stream;;

    - : Genlex.token = Float 1.

    # Stream.next token_stream;;

    - : Genlex.token = Kwd "+"

    # Stream.next token_stream;;

    - : Genlex.token = Ident "x"

Анализатор сопоставляет поток токенов с образцом. Чтобы учесть приоритет и ассоциативность операторов, в рекурсивных нисходящих анализаторах используются промежуточные функции разбора. Обработка потока предпочтительнее обработки регулярной структуры данных, поскольку позволяет рекурсивно вызывать функции разбора внутри образцов, выделяя подкопмоненты потока. Подробнее об этом написано в документации Camlp4.

    #let rec parse_expr = parser
              [< e1 = parse_mult; e = parse_more_adds e1 >] -> e
     and parse_more_adds e1 = parser
         [< 'Kwd "+"; e2 = parse_mult; e = parse_more_adds (Sum(e1, e2)) >] -> e
       | [< 'Kwd "-"; e2 = parse_mult; e = parse_more_adds (Diff(e1, e2)) >] -> e
       | [< >] -> e1
     and parse_mult = parser
         [< e1 = parse_simple; e = parse_more_mults e1 >] -> e
     and parse_more_mults e1 = parser
         [< 'Kwd "*"; e2 = parse_simple; e = parse_more_mults (Prod(e1, e2)) >] -> e
       | [< 'Kwd "/"; e2 = parse_simple; e = parse_more_mults (Quot(e1, e2)) >] -> e
       | [< >] -> e1
     and parse_simple = parser
         [< 'Ident s >] -> Var s
       | [< 'Int i >] -> Const(float i)
       | [< 'Float f >] -> Const f
       | [< 'Kwd "("; e = parse_expr; 'Kwd ")" >] -> e;;

    val parse_expr : Genlex.token Stream.t -> expression = <fun>
    val parse_more_adds : expression -> Genlex.token Stream.t -> expression =
      <fun>
    val parse_mult : Genlex.token Stream.t -> expression = <fun>
    val parse_more_mults : expression -> Genlex.token Stream.t -> expression =
      <fun>
    val parse_simple : Genlex.token Stream.t -> expression = <fun>

    # let parse_expression = parser [< e = parse_expr; _ = Stream.empty >] -> e;;

    val parse_expression : Genlex.token Stream.t -> expression = <fun>

Совместив лексический и синтаксический анализаторы, мы получаем функцию, читающую выражение из символьной строки:

    # let read_expression s = parse_expression(lexer(Stream.of_string s));;

    val read_expression : string -> expression = <fun>

    # read_expression "2*(x+y)";;

    - : expression = Prod (Const 2., Sum (Var "x", Var "y"))

Загадка: почему следующие примеры дают разные результаты?

    # read_expression "x - 1";;

    - : expression = Diff (Var "x", Const 1.)

    # read_expression "x-1";;

    Exception: Stream.Error "".

Ответ: обобщенный анализатор из модуля `Genlex` считает отрицательный целочисленный литерал одним токеном. Поэтому `"x-1"` читается как `Ident "x"` и `Int(-1)`, а такая последовательность не соотвествует ни одному правилу. Дополнительные пробелы в выражении `x - 1` помогают анализатору найти правильную последовательность из трех токенов: `Ident "x"`, `Kwd "-"` и `Int 1`.

## 1.9 Самостоятельные программы на Caml

До сих пор все примеры исполнялись в интерактивной системе. Но код Ocaml также может быть скомпилирован компиляторами `ocamlc` и `ocamlopt`. Исходные тексты обычно хранятся в файлах с расширением `.ml`. Они состоят из фраз, которые вычисляются по порядку следования в файле. В отличие от интерактивного режима, типы и значения не выводятся, поэтому программа должна явно вызывать функции вывода, если это необходимо. Следующая программа выводит числа Фибоначчи:

    (* Файл fib.ml *)
    let rec fib n =
      if n < 2 then 1 else fib(n-1) + fib(n-2);;
    let main () =
      let arg = int_of_string Sys.argv.(1) in
      print_int(fib arg);
      print_newline();
      exit 0;;
    main ();;

`Sys.argv` - это массив строк, содержащий аргументы командной строки. Таким образом, `Sys.argv.(1)` - первый аргумент. Программа компилируется и выполняется следующими командами:

    $ ocamlc -o fib fib.ml

    $ ./fib 10

    89

    $ ./fib 20

    10946