В наборе с каждым языком программирования идут программы, которые могут быть использованы программистом — они называются библиотеками. Количество и качество библиотек является одним из основных критериев удобства использования языка. Библиотеки можно разбить на два типа. В первый тип библиотек предоставляет типы и функции частого использования и которые могут быть определены языком. Второй — предоставляющий возможности, которые не могут быть определены языком. Иначе говоря, при помощи первого типа библиотек, мы избавляемся от переопределений таких вещей как стеки, очередь, и т.д., а второй тип расширяет возможности языка.
Большое количество библиотек поставляется в дистрибутиве Objective CAML. Они распространяются в виде скомпилированных файлов. Однако, любознательный читатель может найти исходные файлы библиотек в дистрибутиве исходников языка.
Библиотеки Objective CAML организованы по модулям, которые в свою очередь являются элементами компиляции. Каждый из них содержит глобальные определения типов, исключений и значений, которые могут быть использованы в программе. В данной главе, мы не будем рассматривать построение подобных модулей, а лишь использование существующих. В 13 главе мы вернемся к концепции модуля (логический элемент) и элемента компиляции и опишем язык описания модулей в Objective CAML. А в 11 мы обсудим включение кода написанного на другом языке в библиотеки Objective CAML, в частности интеграция кода на C в Objective CAML.
В дистрибутив Objective CAML входит preloaded библиотека (модуль Pervasives), набор базовых модулей, называемых стандартной библиотекой и много других библиотек, добавляющих дополнительные возможности. Некоторые библиотеки лишь упоминаются в данной главе или они описываются в следующих главах.
В этой главе мы опишем библиотеки входящие в дистрибутив Objective CAML. Некоторые из них уже были представлены в предыдущих главах, например Graphics (см. главу 4) или лишь упомянуты, как библиотека Array. В первом разделе описана организация различных библиотек. Во втором разделе мы рассмотрим preloaded модуль Pervasives. В третьем разделе мы сделаем классификацию множества модулей, сгруппированных в стандартной библиотеки. И наконец в четвертом разделе подробно рассмотрим библиотеки точной арифметики и динамической загрузки кода.
Библиотеки дистрибутива Objective CAML могут быть разделены на три части. В первой содержатся preloaded глобальные объявления. Вторая, так называемая стандартная библиотека, имеет достаточно высокий уровень стабильности. Она разделена на 4 части:
В третьей части библиотек находятся модули расширяющие возможности языка, как например библиотека Graphics (см. главу 4). В этой части мы можем найти библиотеки для обработки регулярных выражений (Str), точной арифметики (Num), системных вызовов Unix (Unix), нитей (Threads) и динамической загрузки byte–code (Dynlink).
Операции ввода/вывода и системный интерфейс стандартной библиотеки совместимы с различными операционными системами: Unix, Windows, MacOS. Не все библиотеки третьей группы обладают такой особенностью. Также существует множество библиотек поставляемых независимо от дистрибутива Objective CAML.
Для того, чтобы использовать модуль или библиотеку в программе, используют синтаксис с точкой, указав имя модуля, а затем имя объекта. К примеру, если необходим объект f из модуля Name, то мы пишем Name.f. Чтобы постоянно не добавлять префикс из имени модуля, можно открыть модуль и вызывать f напрямую.
Синтаксис 10cmopen Name
С этого момента, все объявления модуля Name будут рассматриваются как глобальные в текущем окружении. Если какое-то объявление имеет одно и то же имя в двух библиотеках, то имя последней загруженной библиотеки перекрывает предыдущее объявление. Чтобы вызвать первое объявление, необходимо использовать синтаксис с точкой.
Библиотека Pervasives всегда автоматически подгружается, как в интерактивной среде, так и при компиляции нативным компилятором. Эта библиотека всегда связывается (linked) и является начальным окружением языка. В ней содержатся следующие объявления:
А так же системные исключения:
Отслеживание исключения Stack_overflow меняется в зависимости от того как была скомпилирована программа: нативно или в byte–code. В первом случае это не возможно.
В стандартной библиотеке сгруппированы стабильные, платформонезависимые модули. На данный момент библиотека содержит 29 модулей, которые в свою очередь состоят из 400 функций, 30 типов, половина из которых абстрактные, 8 исключений, 10 под–модулей и 3 параметризованных модуля. Конечно, мы не будем детально рассматривать все объявления модулей, для этого существует справочное руководство [LRVD99]. Представим лишь модули вводящие новые концепции или сложные в использовании.
Стандартная библиотека может быть разбита на 4 большие части:
К этим четырем частям добавим пятую, в которой содержатся полезные функции манипуляции или создания структур данных, как например функция текстовой обработки или генерации псевдо–случайных чисел и т.д.
Модули, которые мы назовем <<полезными>>, содержат:
При помощи модуля Random можно генерировать случайные числа. Он реализует функцию генерации чисел, которая производит начальную установку счетчика при помощи списка чисел или одного числа. Для того чтобы эта функция не выдавала одно и тоже число, программист должен инициализировать ее разными числами. Основываясь на этом числе, функция вычисляет список случайных чисел. Однако функция, инициализированная одним и тем же числом, выдаст одинаковый список. Поэтому, для корректной установки счетчика, нам понадобится внешний источник, как например компьютерное время или время прошедшее с момента запуска прогаммы.
Ниже приведены функции модуля:
В следующем списке представлены модули линейных структур данных:
Параметризованные модули построены на основе других модулей, благодаря чему они становятся еще более общими (generic). Создание параметризованных модулей будет представлено в главе 13 на стр. ??.
Имя модуля указывает на структуру данных, которыми он умеет манипулировать. Если тип абстрактный, то есть представление типа спрятано, то в соответствии с принятым соглашением, подобный тип именуется t внутри модуля. Такие модули реализуют следующие структуры:
Укажем так же последний модуль манипулирующий линейными структурами данных:
За исключением модуля Sort, все остальные модули определяют структуры данных, функции создания таких структур и доступа к элементам этих структур, а так же функции манипуляции, включая функцию перевода в другие типы данных. Только модуль List обходится без физического изменения данных. Мы не станем давать полное описание всех функций, ограничимся лишь семейством функций, используемых этими модулями. Модули List и Array будут рассмотрены в деталях, так как это наиболее часто встречающиеся структуры в императивном и функциональном программировании.
Следующие функции можно найти во всех или почти всех модулях:
По тому же принципу, в нескольких модулях можно встретить функции просмотра и обработки элементов:
Для структур с индексированными элементами существуют следующие функции.
Описывая функции этих библиотек, мы сакцентируем внимание на особенности и общие черты каждой из них. Для функций одинаковых для обоих модулей тип t означает 'a list или a' array. Для функций специфичных одному модулю воспользуемся синтаксисом с точкой.
Первая из них - подсчет длинны.
lenght | : | 'a t-> int |
Две функции для конкатенации двух структур или всех структур списка.
append | : | 'a t-> 'a t-> 'a t |
concat | : | 'a t list -> 'a t |
Каждый модуль включает функцию доступа к элементу структуры по позиции.
List.nth | : | 'a list -> int -> 'a |
Array.get | : | 'a array -> int -> 'a |
В связи с тем что Функция для доступа к i-тому элементу вектора t часто используется у нее есть укороченный синтаксис: t.(i).
Две функции, позволяющие выполнить определенное действие (функцию) над всеми элементами структуры.
iter | : | ('a -> unit) -> 'a t -> unit |
map | : | ('a -> 'b) -> 'a t -> 'b t |
Чтобы вывести все элементы списка или вектора, воспользуемся iter.
Функция map создает новую структуру, которая содержит результаты применения функции к элементам списка или вектора. Проверим это на векторе с изменяемым содержимым:
При помощи следующих итераторов можно создать частичное применение функции для каждого элемента вектора:
fold_left | : | (('a -> 'b -> 'a) -> 'a -> 'b t -> 'a |
fold_right | : | (('a -> 'b -> 'b) -> 'a t -> 'b -> 'b |
Этим итераторам необходимо указать базовый случай со значением по умолчанию, которое будет возвращено в случае если структура пустая.
fold_left f r [v1; v2; ...; vn] = f ... ( f (f r v1) v2 ) ... vn fold_right f [v1; v2; ...; vn] r = f v1 ( f v2 ... (f vn r) ... )
При помощи этих функций можно легко превратить бинарную функцию в n–арную. Для коммутативной и ассоциативной операции итерация слева на право или справа налево одинакова:
Отметим, что пустой лист является нейтральным элементом слева и справа для двоичного сложения. Для этого частного случая следующие выражения эквивалентны:
Таким образом мы “создали” функцию List.concat.
Следующие полезные функции находятся в модуле List.
List.hd | : | 'a list -> 'a |
первый элемент списка | ||
List.tl | : | 'a list -> 'a |
список, без первого элемента | ||
List.rev | : | 'a list -> 'a list |
список в обратном порядке | ||
List.mem | : | 'a -> 'a list -> bool |
тест на принадлежность списку | ||
List.flatten | : | 'a list list -> 'a list |
“разложить” список списков | ||
List.rev_append | : | 'a list -> 'a list -> 'a list |
то же что и append (rev l1) l2 |
Первые две функции являются частичными, если им передать пустой список, то исключение Failure будет возбуждено. Существует вариант mem: memq, использующий физическое равенство.
В модуле List имеется два особенных итератора, обобщающих булеву конъюнкцию и дизъюнкцию (and/or): List.for_all и List.exists определяются следующим образом.
В модуле List имеется различные варианты итераторов (iter2, map2, etc.), которые принимают на входе два листа и обходят их параллельно. В случае если листы имеют разную длину, возбуждается исключение Invalid_argument.
Следующие функции позволяют осуществлять поиск элемента в списке при помощи критерия в виде булевой функции:
List.find | : | ('a -> bool) -> 'a list -> 'a |
List.find_all | : | ('a -> bool) -> 'a list -> 'a list |
У функции find_all существует псевдоним filter.
A variant of the general search function is the partitioning of a list:
List.partition | : | ('a -> bool) -> 'a list -> 'a list * 'a list |
Часто используемые функции из модуля List для разбиения или создания списка из списка пар:
List.split | : | ('a * 'b) list -> 'a list * 'b list |
List.combine | : | 'a list -> 'b list -> ('a * 'b) list |
И наконец, часто используемая структура, совмещающая список с парой: список ассоциаций. Такая структура удобна когда необходимо хранить данные ассоциированные ключу. Она состоит из списка пар, первый элемент которой ключ, а второй - связанное с этим ключом значение. Для подобных списков имеются следующие функции:
List.assoc | : | 'a -> ('a * 'b) list -> 'b |
извлекает информацию связанную с ключом | ||
List.mem_assoc | : | 'a -> ('a * 'b) list -> bool |
проверяет существование ключа | ||
List.remove_assoc | : | 'a -> ('a * 'b) list -> ('a * 'b) list |
удаляет элемент связанный с ключом |
У каждой из этих функций существует аналог, использующий физическое равенство вместо структурного: List.assq, List.mem_assq и List.remove_assq
Векторы, так часто используемые в императивном программировании, являются физически изменяемыми структурами. В модуле Array имеется функция для изменения значения элемента:
Array.set | : | 'a array -> int -> 'a -> unit |
Как и функция get, у функции set имеется укороченная запись: t.(i) <- a
Существует 3 функции выделения памяти под вектор:
Array.create | : | int -> 'a -> 'a array |
создать вектор заданного размера, все элементы которого инициализированы одинаковым значением | ||
Array.make | : | int -> 'a -> 'a array |
укороченная запись для create | ||
Array.init | : | int -> (int -> 'a) -> 'a array |
создать вектор заданного размера, все элементы которого инициализированы результатом функции от индекса инициализируемого элемента |
Так как матрицы это часто встречающаяся структура, в модуле Array имеется две функции создания матриц:
Array.create_matrix | : | int -> int -> 'a -> 'a array array |
Array.make_matrix | : | int -> int -> 'a -> 'a array array |
Функция set превращается в функцию изменяющую значения интервала, который указан индексом началом и длиной:
Array.fill | : | 'a array -> int -> int -> 'a -> unit |
При помощи следующих функций можно скопировать целый вектор или часть вектора, указанного началом и длиной. При этом мы получаем новую структуру:
Array.copy | : | 'a array -> 'a array |
Array.sub | : | 'a array -> int -> int -> 'a array |
Копия или вырезка могут реализовываться и в существующий вектор:
Array.blit | : | 'a array -> int -> 'a array -> int -> int -> unit |
Первый целочисленных аргумент является индексом в первом векторе, второй аргумент - индекс второго вектора и третий число копируемых элементов. Функции blit, sub и fill возбуждают исключения Invalid_argument.
В связи с тем что к элементам массива обычно доступаются при помощи индекса, были определены следующие два итератора:
Array.iteri | : | (int -> 'a -> unit) -> 'a array -> unit |
Array.mapi | : | (int -> 'a -> 'b) -> 'a array -> 'b array |
Они применяют функцию, первый аргумент которой является индекс желаемого элемента.
В модуле Array не имеется функции–аналога map для списков, которая бы могла изменить содержимое каждой ячейки вектора на результат функции от значения этой ячейки. Но мы можем получить тоже самое при помощи функции iteri:
И наконец, модуль Array предоставляет две функции для создания списка из вектора и наоборот.
Array.of_list | : | a list -> 'a array |
Array.to_list | : | a array -> 'a list |
В стандартой библиотеке имеется четыре модуля ввода–вывода
Детали модуля Marshal будут приведены позже в этой главе, когда мы займемся обработкой постоянных данных (persistent data structures) (см. стр. ??).
При помощи модуля Printf мы можем форматировать текст на манер функции printf из стандартной библиотеки языка C. Желаемый формат представлен строкой, которая будет обработана в соответствии со стандартами функции printf, то есть используя символ %. Этот символ, если за ним следует буква, определяет тип данных на данной позиции. Следующая запись “(x=%d, y=%d)” означает что вместо %d нужно вывести два целых числа.
Формат указывает параметры для выводимой строки. Базовые типы: int, float, char и string будут преобразованы в строки и подставлены на их места в распечатываемой строке. В результате передачи значений 77 и 43 формату “(x=%d, y=%d)” получим строку “(x=77, y=43)”. Основные символы, определяющие тип преобразования приведены в таблице 7.1.
Тип Символ Результат целое d или i десятичное со знаком u десятичное без знака x шестнадцатеричное без знака в нижнем регистре X то же самое в верхнем регистре символ c символ строка s строка действительное f десятичное e или E экспоненциальная запись g или G то же самое логический b true или false специальный a или t функциональный параметр типа (out_channel -> 'a -> unit) -> 'a -> unit или out_channel -> unit
Table 7.1: Символы преобразования типов
В формате можно так же установить выравнивание выводимых строк. Для этого необходимо указать размер в символах преобразования типа. Делается это так: между символом % и типом преобразования вставляется число, например %10d, после чего мы получаем выравнивание справа по десяти символам. Если размер результата превосходит размер ограничения, то это ограничение будет проигнорировано. Отрицательное значение реализует выравнивание слева. Для перевода действительных чисел стоит указать точность результата. В этом случае после % вставим точку и целое число. Следующая запись %.5f указывает, что мы желаем пять чисел после запятой.
Существует два специальных формата: a и t, они указывают функциональный аргумент. Typically, a print function defined by the user. В этом заключается специфичность Objective CAML.
Тип пяти функций модуля Printf приведен в таблице 7.2.
Функции fprintf ожидает три параметра: имя канала, формат и аргументы с типами, описанными форматом. Функции еprintf и printf есть специализированные версии для стандартного выхода и стандартного выхода ошибок. И наконец, sprintf и bprintf не выводят результат на экран, а записывают его в строку.
Вот несколько примеров использования различных форматов:
В следующем примере, создадим функцию для распечатки матрицы вещественных чисел в заданном формате:
Описание формата задается в виде символьной строки, но оно не является типом string. При распознавании формата создается значение с типом format, в котором параметр 'a реализуется либо в unit, если формат не указывает параметр, либо в функциональный тип, который соответствует функции с нужным количеством аргументов и возвращаюшей значение unit.
Проиллюстрируем это на примере, частично применив функцию к формату.
Таким образом мы получили функцию ожидающую три аргумента. Заметьте, что слово “begin” уже было выведено. Другой формат приводит к созданию функции другого типа.
Передавая аргументы, одни за одним функции p3 получим постепенный результат:
Последнее получено значение ничего не выводит, оно возвращает значение () типа unit.
Мы не сможем создать значение format используя значения типа string:
Компилятор не знает значение строки, переданной в аргументе, в следствии чего он не знает тип который реализует параметр 'a типа format.
С другой стороны, строки символов являются физически изменяемым типом, то есть мы можем поменять %d на другую букву, таким образом динамически изменить формат вывода. Это не совместимо со статической генерацией функции перевода.
Из символьной строки, какого угодно размера, хэш функции возвращает строку фиксированного размера, чаще всего меньшего размера. Такие функции возвращают отпечаток (digest).
Такие функции используются для создания хэш таблиц, как на пример модуль Hashtbl, позволяющий проверить присутствует ли определенный элемент в таблице прямым доступом (direct access) используя отпечаток. Например, функция f_mod_n подсчитывает остаток от деления суммы кодов сомволов ASCII строки на n и является хэш функция. Если мы создадим таблицу размером n, то благодаря отпечаткам получим прямой доступ к элементам. Однако, две строки могут иметь одинаковый отпечаток. В случае подобных коллизий, в хэш таблицу добавляется дополнительное “хранилище” для хранения таких элементов. Когда таких коллизий становится много, доступ к таблице становится малоэффективным. Если n — размер отпечатка a, тогда вероятность возникновения коллизий между двумя разными строка равна 1/2n.
Вероятность возникновения коллизий у non–reversible хэш функции очень мала, таким образом трудно подобрать строку к конкретному отпечатку. Конечно, функция f_mod_n таковой не является. Таким образом non-reversible хэш функции позволяют идентифицировать (authentification) символьную строку полученную по Интернету, файл, и т.д.
Модуль Digest использует алгоритм MD5, как аббревиатура Message Digest 5. Размер возвращаемого отпечатка 128 бит. Несмотря на то, что алгоритм свободно доступен, на сегодняшний день невозможно создать строку имея отпечаток. В этом модуле определен тип Digest.t как аббревиатура string. В таблице 7.3 приведены основные функции этого модуля.
В следующем примере мы используем функцию string на небольшой строке и на большой строке, построенной из первой. Заметьте, что размер отпечатка остается такой же:
Применяя отпечатки программ, мы тем самым гарантируем, что версия используемой программы правильная. К примеру, во время динамической загрузки кода (см. стр. ??), используются отпечатки для загрузки файла с байт–кодом.
Сохраняемость это способность сохранять значения переменных когда программа не выполняется. Примером может служить сохранение переменных в файле. 3та переменная доступная любой программе, которая может читать этот файл. Чтение и запись сохраняемого значения требует определенный формат данных. Необходимо определить способ сохранения сложной структуры в памяти компьютера, например дерева, в линейную структуру — последовательность байтов в файле. По этой причине кодировка сохраняемых значений называется линеаризацией 1.
При реализации механизма линеаризации данных необходимо сделать определенный выбор и встречаются следующие трудности:
Механизм линеаризации модуля Marshal предоставляет нам выбор: сохранить или нет разделение для обрабатываемых значений. Он так же умеет обращаться с замыканиями, но здесь сохраняется лишь указатель на адрес по которому находится код.
Этот модуль состоит в основном из функций линеаризации в какой–нибудь канал либо строку (string) и функции получающие данные из канала или строки. Функции линеаризации параметризуются. У следующего типа может быть два возможных случая:
Константный конструктор No_sharing указывает что разделение значений не должно быть сохранено, так как по умолчанию разделение сохраняется. Конструктор Closures необходим когда мы имеем дело с замыканиями, в этом случае сохраняется указатель на код. Если этот конструктор отсутствует и мы попытаемся сохранить функциональное значение, то будет возбуждено исключение.
Warning
Конструктор Closures не может использоваться в интерактивном
режиме, а лишь в командной строке.
Функции записи и чтения данного модуля приведенные в таблице
7.4.
У функции to_channel три аргумента: выходной канал, значение и список опций, она записывает переданное значение в указанный канал. Функция to_string создает строку, которая соответствует линеаризации указанного значения, тогда как to_buffer делает то же самое, изменяя часть строки переданной в аргументе. Функция from_channel возвращает прочитанное из канала линеаризованное значение. У функции from_string два аргумента: строка из которой необходимо прочитать значение и позиция в строке с которого начинается это значение. В файле или строке можно сохранить Несколько линеаризованных значений. В первом случае такие значения читаются последовательно,во втором достаточно указать правильный отступ от начала строки, чтобы прочитать необходимое значение.
Warning
Использование данного модуля приводит к потере гарантии статической
типизации (см. стр. ??)
При перечитывании сохраняемого объекта получаем значение неопределенного типа:
Эта неопределенность отмечена в Objective CAML переменной нестрого типа 'a. Поэтому желательно указать ожидаемый тип:
На странице ?? мы вернемся к этому моменту.
Замечание
Функция output_value подгружаемой библиотеки соответствует
вызову to_channel с пустым списком опций. Функция
input_value модуля Pervasives вызывает функцию
from_channel. Они сохранены для совместимости со старыми
программами.
Наша задача — сохранить bitmap всего экрана в виде матрицы цветов. Функция save_screen забирает bitmap, конвертирует его в вектор цветов и сохраняет в файле, имя которого преданно функции.
Функция load_screen выполняет обратную операцию; открывает файл, имя которого указанно, считывает сохраненные данные, конвертирует полученную матрицу цветов в bitmap и затем рисует на экране.
Warning
Значения абстрактных типов не могут быть сохраняемыми.
По этой причине в предыдущем примере не используется абстрактный тип
Graphics.image, а конкретный тип color array
array. Абстракция типов обсуждается в главе 13.
Потеря данного свойства для значения может порой привести к тому что оно станет неиспользуемым. Вернемся к примеру генератора символов на стр. ??. Пусть нам необходимо сохранить функциональные значения new_s и reset_s, для того чтобы снова получить значения этих счетчиков. Напишем следующий код:
Первые два вывода (* 1 *) выводят правильный результат. Вывод (* 2 *), после прочтения замыкания тоже кажется корректным (после X2 следует X3). Но на самом деле разделение счетчика c между функциями new_s1 reset_s1 утеряно. Об этом мы можем судить по выводу X4, несмотря на то что мы обнулили перед этим счетчик. Каждое из замыканий имеет свой собственный счетчик и вызов reset_s1 не инициализирует счетчик new_s1. То есть мы не должны были указывать опцию No_sharing во время линеаризации.
Данный пример не работает, код соответствующий приведенному выше тексту следующий (прим. пер.):
Чаще всего необходимо сохранять разделение. Однако, там где важна скорость выполнения отсутствие разделения ускоряет сохранение значений. В следующем примере функция копирует матрицу. В данном случае желательно избавится от разделения.
Мы можем воспользоваться этой функцией для создания матрицы без разделения:
Поведение этой функции больше сходит на Array.create_matrix чем Array.create.
Порой необходимо бывает знать размер сохраняемого значения. Если разделение сохранено, то указанный размер достаточно точно отображает информацию. Хоть кодировка порой оптимизирует размер непосредственных 2 (или элементарных) значений, информация о размере их кодировки позволяет нам сравнить различные реализации структур данных. С другой стороны, для программ, которые выполняются без остановки, как во встроенных системах или даже серверах, просмотр размер структур данных помогает отловить утечки памяти. Две функции для вычисления размера и константа модуля Marshal описаны в таблице 7.5.
Размер сохраняемого значения есть сумма размера его данных и размер заголовка.
В следующем примере мы приводим сравнение двух бинарных деревьев при помощи функции MD5:
Полученые при помощи функции size значения совпадают с предполагаемым размером деревьев s1 и s2.
Действительной проблемой сохраняемых значений является возможность нарушить механизм типизации Objective CAML. Функции записи создают правильный мономорфный тип: unit и string. Тогда как функции чтения возвращают полиморфный тип 'a. С таким сохранянемым значением мы можем сделать что угодно. Вот самый худший вариант (см. гл. 1 на стр. ??): создание функции magic_copy с типом 'a -> 'b.
Использование подобной функции приведет к аварийной остановке программы:
В интерактивной среде (в Линуксе) текущая сессия заканчивается с сообщением об ошибке памяти.
В стандартной библиотеке содержится 6 модулей системного интерфейса:
Описание четырех первых модулей приведено ниже.
Данный модуль содержит полезные функции для взаимодействия с операционной системой, а так же для обработки сигналов, полученных программой. В таблице 7.6 приведены значения содержащие информацию о системе.
OS_type : string : тип системы interactive : bool ref : истина, если интерактивный режим word_size : string : размер слова (32 или 64 бита) max_string_length : int : максимальный размер символьной строки max_array_length : int : максимальный размер вектора time : unit -> float : время в секундах, истекшее с начала выполнения программы
Table 7.6: Данные об операционной системе
Связь между программой и операционной системой осуществляется через командную строку, переменную окружения и запуском другой программы. Эти функции описаны в таблице 7.7.
При помощи Функции в таблице 7.8 можно “использовать” файловую систему.
Обработка сигналов будет описано в специальной главе о системном программировании (17).
Напишем следующую программу, в ней мы вновь вернемся к примеру с сохранением графического окна в виде вектора цветов. Функция main проверяет не запущена ли она в интерактивном режиме, затем считывает имена файлов в командной строке, проверяет существуют ли они и затем выводит их на экран (при помощи функции load_screen). Между двумя выводами, мы вставляем ожидание нажатия на клавишу.
Модуль Arg определяет синтаксис для анализа аргументов командной строки. В нем имеется функция для синтаксического анализа, а так же определение действия, которое будет связано с анализируемым элементом.
Элементы командой строки разделены между собой одним или несколькими пробелами. Все они хранятся в векторе Sys.argv. В синтаксисе определенном Sys.argv некоторые элементы начинаются знаком минус ('-'). Такие элементы называются ключевыми словами командной строки. Ключевым словам можно привязать определенное действие, которое ожидает аргумент с типом string, int или float. Значения аргументов инициализируются значениями командной строки, которые непосредственно следуют за ключевым словом. В таком случае вызывается функция перевода значения из строки в ожидаемый тип данных. Другие аргументы командной строки называются анонимными. Им ассоциируется общее действие, которое принимает их значение в виде аргумента. Если указана неопределенная опция, то на экран выводится небольшая помощь, которая определяется пользователем.
Действия, связанные с ключевыми словами определены следующим типом:
Для анализа командной строки существует следующая функция:
Первым аргументом является список кортежей формы (key, spec, doc), где:
вторым аргументом передается функция обработки анонимных аргументов командной строки. Последним аргументом является строка, которая выводится в заголовке помощи.
В модуле Arg имеется так же:
Примера ради, напишем функцию read_args, которая инициализирует игру Minesweeper, представленую в главе 5, страница ??. Возможные опции будут следующими: -col, -lin и -min. За ними буду следовать целые числа, указывающие число столбцов, число линий и число мин соответственно. Эти значения не должны быть меньше значений по умолчанию: 10,10 и 15.
Для этого создадим три функции обработки:
У всех этих функций одинаковый тип: config ref -> int -> unit. Функции анализа командной строки можно написать следующимобразом:
При помощи переданных параметров эта функция вычисляет конфигурацию, которая затем будет передана функции открывающей графическое окно open_wcf при запуске игры. Каждая из опций, не является обязательной, как на то указывает само слово опция. Если одна из них отсутствует в командной строки, то будет использоваться значение по умолчанию. Порядок опций не имеет значений.
В модуле Filename реализованы операции по доступу к файловой системе, независимо от операционной системы. Правила по которым называются имена в Windows, Unix и MacOS сильно различаются.
3том совсем небольшой модуле из трех функций (таблица 7.9) предоставляет нам сборщик исключений. Он очень удобен, особенно для программ запускаемых с командной строки3, чтобы быть уверенным в том что ни одно исключение не будет упущено и это не приведет к остановке программы.
Функция catch применяет свой первый аргумент ко второму, это приведет к запуску основной функции программы. Если исключение будет возбуждено на уровне catch, то есть не выловленное внутри программы, то catch выведет свое имя и прекратит программу. Функция print ведет себя схожим образом, с разницей в том что после вывода на экран она снова возбуждает исключение. И наконец функция to_string переводит исключение в строку символов. Она используется двумя предыдущими функциями. Если бы мы переписывали программу просмотра bitmap, то мы бы скрыли функцию main внутри функции go:
Это позволит нормально закончить программу и вывести при этом значение неотловленного исключения.
Другие библиотеки, входящие в дистрибутив Objective CAML, затрагивают следующие расширения:
При помощи приведенных примеров мы опишем бибиблиотеки для работы с большими числами и динамической загрузки.
Данная библиотека обеспечивает точную арифметику при работе с большими числами; целыли или рациональными. У значений типа int и float существует предел. Вычисления целых чисел реализуются по модулю самого большого положительного числа, что может привести к незамеченному переполнению. Результ вычисления чисел с плавающей запятой округляется, при распространении этот феномен также может привести к ошибкам. Данная библиотека смягчает выше указанные недостатки.
Эта библиотека написана на языке C, поэтому нам необходимо создать новую интерактивную среду, в которую включен следующий код:
Билиотека состоит из несколькох модулей, два наиболее важных из них это Num для всех операций и Arith_status для контроля опций расчета. Общий тип num является тип сумма группирующая три следующих типа:
Типы big_int и ratio являются абстрактными.
После операций со значениями типа num ставится символ '/'. Например сложение двух значений num запишется как +/, тип этой операции num -> num -> num. Для операций сравнения применяется аналогичное правило. Вслеующем примере, мы вычисляем факториал:
Предварительная загрузка модуля Num облегчает читаемость кода:
Вычисление рациональных чисел так же является точным. Для того чтобы вычислить число е по следующей формуле:
e= |
| (1+ |
| )m |
напишем такую функцию:
При помощи модуля Arith_status мы можем контролировать вычисление: округление результата для вывода, нормализация рациональных чисел и обработка нулевого знаменателя дроби. При помощи функции arith_status мы можем знать состояние указанных опций.
В зависимости от необходимости, эти опции могут быть изменены. К примеру если мы желаем выводить на экран приближенное значение предыдущего расчета:
Вычисление больших чисел занимает больше времени чем для обычных целых, а так же значения занимают больше места в памяти. Однако, данная библиотека старается делать представление данных наиболее экономичным. В любом случае, наша цель, избежать распространение ошибок из-за округления и возможность проводить вычисления над большими числами, оправдывает потерю эффективности.
Для динамической загрузки программ, в виде байт–кода, существует библиотека Dynlink. Преимущества у динамической загрузки кода следующие:
Интерактивная среда Objective CAML использует подобный механизм, почему бы и разработчик на Objective CAML не имел подобных удобств.
Во время загрузки объектного файла (с расширением .cmo), вычисляются различные выражения. Программа, которая загружает модуль, не имеет доступа к именам модуля, поэтому обновление таблицы имен основной программы лежит на ответственности загружаемого модуля.
Warning
Динамическая загрузка кода может быть действует лишь для байт–код
объектов.
Для того чтобы загрузить файл f.cmo, содержащий байт–код, необходимо с одной стороны знать путь по которому находится данный файл, и имена модулей которые он использует с другой стороны. Динамические загружаемые байт–код файлы не знают путь по которому находятся модули стандартной библиотеки и имена модулей. Поэтому необходимо предоставить им эту информацию.
init : unit -> unit : инициализация динамической загрузки add_interfaces : string list -> string list -> unit : добавление имен модулей и путь по которому находится стандартная библиотека loadfile : string -> unit : загрузка байт–код файла clear_avalaible_units : unit -> unit : удаляет имена модулей и путь по которому находится стандартная библиотека add_avalaibl_units : (string * Digest.t) list -> unit : добавить имя модуля и отпечаток для загрузки, без интерфейсного файла allow_unsafe_modules : bool -> unit : загружать файлы содержащие внешние объявления loadfile_private : string -> unit : загруженный модуль не будет доступен для следующих загруженных модулей
Table 7.10: Функции модуля Dynlink
В момент динамической загрузки, может возникнуть множество ошибок. Кроме того, что загружаемый файл и интерфейсный файлы должны существовать по искомым каталогам, байт–код должен быть корректным и загружаемым. Подобные ошибки сгруппированы в типе error, который используется как аргумент исключения Error и функции error с типом varnameerror -> string. При помощи данной функции мы получаем понятные сообщения об ошибках.
Для того, чтобы проиллюстрировать динамическою загрузку, создадим три модуля:
Модуль F определен в файле f.ml:
Модуль Mod1 определен в файле mod1.ml:
Модуль Mod2 определен в файле mod1.m2:
И наконец определим основную программу в файле main.ml, в которой вызовем функцию на которую указывает F.f, затем загрузим модуль Mod1, снова вызовем функцию F.f, загрузим модуль Mod2 и вызовем в последний раз F.f.
Кроме указанных действий, инициализации динамической загрузки, программа должна объявить используемые интерфейсы вызовом Dynlink.add_interfaces.
Скомпилируем созданные файлы.
Вызов программы main даст следующий результат:
Во время динамической загрузки, происходит выполнения кода. Это проиллюстрированно выводом на экран строк начинающихся на “The 'Mod…”. Возможные побочные эффекты данного модуля наследуются основной программой. По этой причине различные вызовы функции F.f приводят к разным вызовам функций.
Библиотека Dynlink предоставляет базовый механизм динмаической загрузки байт–кода. Программист должен самостоятельно позаботится об управлении таблица, для того чтобы загрузка имела место.
В этой главе мы ознакомились с различными библиотеками стандартной библитеки Objective CAML. Библиотеки представляют из себя множество простых модулей (или отдельных элементов компиляции). Здесь мы детально рассмотрели модули форматированного вывода (Printf), сохраняемости значений (Marshal), системного интерфейса (Sys) и отлавливания исключений (Printexc). Модули систаксического анализа, управления памятью, системное и сетевое програмирование, легковесные процессы будут рассмотрены в следующих главах.