Глава 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