navigation 
Глава
2
Глава
3
Глава
4
Глава
5
Глава
6
Глава
7
Глава
8
Глава
9
Глава
10
Глава
11
Глава
12
Глава
13
Глава
14 

Глава 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 сам при необходимости создает указатели.

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

# 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). То же самое относится к большинству структур данных в Caml, однако существуют и изменяемые (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.05 (2002-07-22)
# 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"

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

Чтобы работать с потоками, необходимо загрузить препроцессор camlp4.

# #load "camlp4o.cmo";;
						
   Camlp4 Parsing version 3.05 (2002-07-22)

Теперь можно писать анализатор.

#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