Тяжело сказать, хорошо или плохо то, что в мире так много языков программирования. С одной стороны, это очередное "вавилонское столпотворение", а с другой — никто ведь не заставляет использовать их все. Многие "аналитики" выбирают функциональные языки, с одним из которых мы сейчас кратко познакомимся.


О ML вообще, и о Ocaml в частности


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


Что характерно: ML представляет собой только описание, спецификацию — так что любая реализация является в равной мере правомерной, пока она удовлетворяет основным принципам. Собственно, сам термин ML=Meta Language уже подразумевает "описание описания".


В качестве языка описания выбраны математические термины — так, чтобы отделить дефиниции языка от самого языка, а также от культурных артефактов английской терминологии. Благодаря этому, как видите, ML получает развитие по всему миру, в частности в России или во Франции. Если вас интересует именно эта, формальная, сторона вопроса (скажем, вы хотите создать собственный диалект), вам понадобится описание языка от его "дизайнеров", доступное на многих книжных полках онлайн: "The Definition of Standard ML, Revised" (
http://mitpress.mit.edu/catalog/item/default.asp?ttype=2&tid=3874).


На данный момент известны и получили распространение следующие версии:

  • SML of New Jersey (самый распространенный вариант);
  • Moscow ML (разработан в институте прикладной математики им. Келдыша, доступен для многих операционных систем, вплоть до Plan9 и платформы .NET.);
  • MLKit;
  • Concurrent ML (расширение SML);
  • и, наконец, предмет нашего рассмотрения — Ocaml, или Objective CaML.

Все эти версии имеют значительные различия. Вы можете убедиться в этом, просмотрев хотя бы перечень отличий между Ocaml и SML-97 (www.ps.uni-sb.de/~rossberg/SMLvsOcaml.html).


Функциональное программирование


Коль уж есть функциональное программирование, то должно быть и какое-то другое — поскольку этот эпитет, пусть и не явно, но все равно подразумевает некое противопоставление. И действительно: функциональное программирование должно — по крайней мере, по мнению его создателей — компенсировать недостатки и сложности императивного программирования.


 Функциональное программирование является разделом дискретной математики, основанной на лямбда-исчислении и комбинаторике. Функции, представляемые лямбда-термами, к которым применяются (или не применяются) операнды, что определяется на основе их комбинирования. К другим языкам из этой же области "логического" или "декларативного" программирования относятся LISP (CLOS, Scheme) и Miranda (Hasskell), Hope и ряд других.


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


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


К сожалению, я смогу рассказать вам лишь о самых поверхностных свойствах Ocaml, которые действительно касаются функционального программирования. Впрочем, оно так глубоко "зашито" во все ML, что без его использования вы не сможете написать даже самой простой программы — подобно тому как невозможно создать программу на Java без использования классов.


Синтаксис: начало


Несмотря на "высокий полет", ML имеет также и свой не особенно сложный (скорее даже примитивный) синтаксис, отличный от синтаксисов большинства других языков — но имеющий и общие черты. Одним из важных и, на первый взгляд, парадоксальных принципов ML является строгая типизация в сочетании с полиморфизмом. Так, для вещественных типов даже введены собственные операторы сложения и умножения (+. и *.), так что вызов неверного оператора вызовет ошибку. Тем не менее, вы часто можете использовать функции со списками и операторами произвольной природы.

 

let a=5*0.7;;
(*ошибка — целочисленное умножение на вещественном операнде*)
let a=5*.0.7;
(*тоже ошибка — целое не приводится к вещественному автоматически*)
let a=float_of_int 5*.0.7;;
(*явное приведение типов*)

 

Явное приведение типов — довольно спорное, на первый взгляд, решение, если не знать, что неявный "кастинг" является основным типом сложных ошибок в любом типе программных проектов. Тем более что даже такое "невинное" приведение, как int<->float является весьма ресурсоемкой задачей, способной заметно "притормозить" любой вычислитель. В ML не поощряется приведение типов без насущной необходимости — только и всего.


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

 

fun 1 2;;
(*так записываются комментарии - это вызов функции и два ее параметра*)

 

Такая конструкция тоже является синтаксически верной:

 

fun (1,2);;
(*это один параметр - пара (tuple) элементов и, возможно, не то, что вам нужно*)

 

С другой стороны, если вы используете суперпозицию функций (то есть одна вызывает другую), то внутреннюю функцию придется заключить в скобки, чтобы ее результат образовывал одно значение:

 

fun3 (fun1 (fun2 argument1) argument2) argument3;;
(*похоже на LISP - не так ли?*)

 

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

 

let average a b = (a+.b)/.2.0;;

 

Как видите, мы не задаем типы параметров и не указываем, что будет результатом. ML сам знает, что делать с нашими числами. Никакого "кастинга" не производится — нужно явно указывать тип операций. Основываясь на типе операций, Ocaml "догадывается" о типе операндов и в дальнейшем четко следит за правильным их использованием. В качестве значения возвращается результат последнего выражения. В интерактивном режиме вы получите:

 

average: float -> float -> float =

 

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


Чтобы лучше понять роль операторов и операндов в ML, представьте, что двуместная операция — это граф из двух вершин. Если обычные языки склонны концентрироваться на самих узлах, то ML больше уделяет внимания ребрам этого графа. Фактически, апологеты ML не говорят о "переменных" в привычном смысле. Главный объект рассмотрения — это операции или функции, а также их всевозможные суперпозиции. В терминах ML переменные не имеют постоянного положения в памяти, поэтому для них не имеет смысл понятие "адрес". Это, скорее, правила вывода: записывая let sum=a+.b;; мы не создаем переменную sum — мы не можем присвоить ей другое значение или еще как-то ее использовать.


Это правило вывода: мы говорим "если нужно вывести sum, сложи вещественные величины a и b". То-есть sum — это, скорее, функция или правило. Определение "переменных" не отличается от определения "функций" — это и есть функции без параметров (точнее, с неявными параметрами из лексической области видимости).


Немного о типах. Помимо уже указанных целого и вещественного типов существуют также строки, символы и логические выражения. Один бит отличает целые от указателей, так что целые стали немного меньше (31 бит), но с указателями их теперь не спутаешь. В ML нет многих "оптимизаций" из C — в частности, беззнаковых целых и коротких вещественных чисел.


Для технических нужд, например для декодирования MP3 или разбора заголовков IP-пакетов, где все биты значимые, существует тип nativeint. В качестве "эмуляции" типа void используется тип unit, что обозначает "блок кода, результат которого нас не интересует".


Рекурсия


В то время как в С или других языках рекурсия и функциональные параметры встречаются редко (или, по крайней мере, время от времени), в ML это естественный образ мысли. Достаточно сказать, что в классическом варианте в языке даже нет конструктива for — предполагается, что его заменяет рекурсия. Классическая рекурсивная функция, возвращающая список целых чисел от a до b включительно записывается таким образом:

 

let rec range a b=
if a>b then [ ]
else a::range (a+1) b;;

 

Здесь есть парочка интересных моментов:

  • [ ] обозначает пустой список;
  • операция :: — конкатенация нового элемента со списком, то есть новый элемент становится "головой" списка (опять аналогия с LISP);
  • оператор rec указывает, что ссылка на range в тексте должна указывать на ту же функцию — в противном случае, возможно, вы хотите указать на что-то, что было определено до этого. Хотя старая функция утратит связь со своим именем, вы по-прежнему сможете вызывать ее анонимно (напоминает вызов виртуальных методов суперкласса в Java, то же имя — но не тот же код).

Полиморфизм и поздняя типизация


В то же самое время, когда ML "не умеет" даже смешивать в одном выражении целые и вещественные числа,— в то же время важной особенностью языка является полиморфизм. Существуют объекты, тип которых не определен до момента выполнения. Они обозначаются апострофом перед символьным именем: например, 'a. На самом деле до этапа выполнения с 'a не связан никакой объект или тип — лучше всего думать о таких параметрах и функциях, как о шаблонах в C.


Вот простой пример функции с полиморфным параметром:

 

let notype x="no type";;
notype = 'a -> string =


Ссылки и связанные с ними операторы


Как уже было сказано, оператор let не приводит к созданию переменных в терминах "адрес и размер" — это ссылка на правило вывода. Тем не менее, вы можете создать реальный объект типа "ссылка", подобный ссылкам в Perl. Такой объект описывается оператором ref. После создания ссылки сразу же присвойте ее какому-то "правилу", иначе она тут же попадет под действие сборщика мусора. Оператор := обозначает "ссылка на", а оператор ! разыменовывает ссылку, поставляя значение — это соответствует операторам & и * в языке C. Например:

 

let a=ref 1;;
(*а — ссылка на 1*)
a:=2;;
(*а — ссылка на 2*)
!a;;
(*значение "на что указывает а"*)


Вложенные фукнции


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

 

let hypo a b=
let quad=c*.c in
(quad a)+.(quad b);;

 

Как видите, фрагменты вложенного текста программы заканчиваются служебным словом in, что является естественным английским оборотом речи. Вы также должны завершать лексические фрагменты (подпрограммы) этим служебным словом — нечто, вроде end; в Pascal.


Пару слов об операторах "точка с запятой". Двойная точка с запятой всегда возвращает вас на верхний уровень программы, как бы "глубоко" вы не находились до этого. Как следствие — эта конструкция никогда не встречается в теле "подпрограмм", она может лишь завершать их определение. Подобно операнду ; в Pascal, существуют случаи, когда ;; можно не использовать, поскольку компилятор и так догадывается, что это начало нового операнда, а не продолжение старого. В частности, ;; можно не ставить перед словами let, open, type, в самом конце файла и еще в некоторых (правда, достаточно редких) случаях. Начинающим, думаю, приятно будет узнать, что лишние ;; никогда не помешают — ставьте, где считаете нужным, даже если не уверены.


 Обычная точка с запятой имеет значение оператор-"последовательность" (формально — unit -> 'b -> 'b) и предписывает завершить "все, что до" и продолжить далее — то есть принять два операнда, два участка кода слева и справа и вернуть значение последнего. В языке С это соответствует запятой. Вообще, в ML почти все представляет собой выражение — не только присвоение, как в C, а также и оператор ; , конструкция if/then/else, конструкция match ... with — короче говоря, почти все возвращает значение.


Будьте внимательны: let ... in — это один оператор с последующими конструкциями, так что никогда не завершайте ее символом ;. Точно также не нужно завершать этим символом последнее предложение в последовательности, например последнюю конструкцию перед завершением цикла done.


Модули


Всегда, когда вы захотите написать что-то реально интересное, вы будете использовать заранее "упакованные" модули, содержащие доступ ко всему, что вам нужно. Диапазон модулей Ocaml может поспорить с модулями Perl и Python, хотя существует масса специфических модулей из области логики и принятия решений. Как всегда, базовые модули осуществляют доступ к POSIX-интерфейсу, базам данных и графическим пользовательским интерфейсам, таким как Gtk.


Для упрощения префиксов вы можете ввести синоним для модуля с помощью конструкции module Gr=Graphics;; . После этого вы ссылаетесь на правила из модуля как обычно: Gr.open_graphics.


Подключить нужную библиотеку можно одним из двух методов: либо в начале использовать конструкцию open имя-модуля;; , или же перед каждым вызовом метода использовать имя модуля в качестве префикса, например Graphics.open_graph " 800x600";; . В моей инсталляции модули находятся в каталоге /usr/local/lib/caml-light — это значительно ограниченная версия, и вы не найдете там большинства "настоящих" библиотек.


Резюме: недостатки и достоинства


Мы рассмотрели самые простые аспекты функционального программирования на примере Ocaml. Как любой другой язык или метод, Ocaml имеет также свои недостатки. Они присущи функциональным языкам вообще. Во-первых, в связи со сложными методами разрешения параметрических и функциональных зависимостей Ocaml может потреблять значительно больше ресурсов, в том числе оперативной памяти, чем C или любой другой императивный язык программирования. Во-вторых (хотя это и не очевидно, но факт) — Ocaml с трудом взаимодействует с пользователем, так же как и другие декларативные языки. Одна из причин кроется в отсутствии мыслимых "элементов управления" для визуализации всего многообразия происходящих процессов. Хотя, так же как и Prolog или LISP, Ocaml может сопровождать логический (символьный) вывод листингами трассировок, по которым можно проследить логику работы.


А вот к числу достоинств Ocaml можно отнести значительно более высокий уровень абстракций, достижимый на этом языке,— возможно, самый высокий среди всех прочих. Кроме того, задачи ML легко распараллеливаются, что открывает дорогу к кластерным вычислениям на системах с массовым параллелизмом.


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


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


Нужна ли вам такая мощность и такие возможности — мне не известно. Но, по крайней мере, если какой-то инструмент и приблизится к решению проблем искусственного интеллекта, то это будет нечто вроде Ocaml — или его потомок. Достичь этого средствами C или Visual Basic лично мне не представляется возможным, разве что вы заново перепишете Ocaml на Бейсике.

2005.03.29
19.03.2009
В IV квартале 2008 г. украинский рынок серверов по сравнению с аналогичным периодом прошлого года сократился в денежном выражении на 34% – до $30 млн (в ценах для конечных пользователей), а за весь календарный год – более чем на 5%, до 132 млн долл.


12.03.2009
4 марта в Киеве компания Telco провела конференцию "Инновационные телекоммуникации", посвященную новым эффективным телекоммуникационным технологиям для решения задач современного бизнеса.


05.03.2009
25 февраля в Киеве компания IBM, при информационной поддержке "1С" и Canonical, провела конференцию "Как сохранить деньги в условиях кризиса?"


26.02.2009
18-19 февраля в Киеве прошел юбилейный съезд ИТ-директоров Украины. Участниками данного мероприятия стали ИТ-директора, ИТ-менеджеры, поставщики ИТ-решений из Киева, Николаева, Днепропетровска, Чернигова и других городов Украины...


19.02.2009
10 февраля в Киеве состоялась пресс-конференция, посвященная итогам деятельности компании "DiaWest – Комп’ютерний світ" в 2008 году.


12.02.2009
С 5 февраля 2009 г. в Киеве начали работу учебные курсы по использованию услуг "электронного предприятия/ учреждения" на базе сети информационно-маркетинговых центров (ИМЦ).


04.02.2009
29 января 2009 года в редакции еженедельника "Computer World/Украина" состоялось награждение победителей акции "Оформи подписку – получи приз!".


29.01.2009
22 января в Киеве компания "МУК" и представительство компании Cisco в Украине провели семинар для партнеров "Обзор продуктов и решений Cisco Small Business"

 

 
 
Copyright © 1997-2008 ИД "Комиздат".