Yoksel: Блог/2008?/08?/05?/ПервыеШагиВOcamlФункции ...
SourceForge.net Logo

Home Page | Изменения / НовыеКомментарии / Справка / Помочь проекту | Вход:  Пароль:  

Блог OCaml?

Первые шаги в O'Caml: функции


Начало здесь: Первые шаги в O'Caml: типы данных


Вообще, Caml – крайне интересный язык, с огромным количеством разных интересных вещей, во многих отношениях уникальный. Сегодня я попробую рассказать о функциях. Вряд ли я могу полно рассказать о функциях O'Caml – я ограничусь вещами, которые были мне интересны, особенно в сравнении с C++.

Карринг

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


Короткая запись:


А теперь рассмотрим функцию от двух аргументов:


Ага, скажете вы, а как же утверждение, что функции принимают только один аргумент?! Сейчас разберемся. Рассмотрим эквивалентную запись данной функции от двух аргументов:


Что мы здесь наблюдаем? Мы наблюдаем функцию от ОДНОГО аргумента, которая возвращает ДРУГУЮ функцию тоже от одного аргумента. Поэтому вычисление подобного выражения:

происходит в два приема:


Подробнее:

получаем результат 21.

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

В результате мы получили функцию от одного аргумента, которую в дальнейшем можем спокойно применять – как и любую другую функцию. (Функция part_add является «частичным применением» функции add2.) Подобный подход к рассмотрению функций от нескольких аргументов называется «каррингом» – в честь математика Хаскелла Карри.


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


Кстати, а какие аналоги карринга существуют в C++? В первую очередь это boost::bind и boost::function. Рассмотрим аналог функции add2 на C++:


Частичное применение функции add2:


Выглядит жутковато, не правда ли? Но это не единственный недостаток средств C++. Дело в том, что при образовании функции – частичного применения другой функции – компилятор Caml'а может, по крайней мере в теории, сделать некоторое «предвычисление» функции и сократить тем самым время вычисления полученной функции. Для C++ это крайне затруднительно или даже невозможно в принципе – с точки зрения компилятора это просто запоминание адреса функции и некоторых ее аргументов, чтобы в будущем тупо вызвать функцию, передав ей сохраненные аргументы.


Далее. Что будет, если ошибиться в написании boost::bind? Мы получим 10-этажное ругательное сообщение, указывающее на место где-то глубоко в недрах boost::bind. Максимум, что это может нам сказать – это то, что «где-то ошибка». Если ошибиться при создании частичного применения в Caml мы получим нормальное информативное сообщение, четко указывающее на ошибку. Почему это происходит – очевидно. Частичное применение функции – это штатная, встроенная в язык возможность, для Caml. Для C++ boost::bind – это большое нагромождение шаблонных структур и функций, в котором что-то не срастается в случае ошибки.

Вывод типов

По отзывам в Caml можно написать многие тысячи строк кода без необходимости указывать типы аргументов вообще где-либо. При этом программа будет полностью статически типизирована, типы всех выражений будут контролироваться на этапе компиляции. Это невероятно облегчает синтаксис программы. Особенно по сравнению с C++. Caml использует систему типов Хиндли-Милнера. На форумах по поводу данной системы приходилось наблюдать в частности такие утверждения:

Доказано, что программы на языке с системой типов Хиндли-Милнера не могут «упасть» с ошибками, связанными с доступом по неверному адресу, нулевому указателю.

Вполне интригующе, однако :) Подтвердить или опровергнуть это утверждение в случае с Caml я пока что не могу. Хотя определенные возможности испортить память при желании в Caml можно получить. Ведь в нем есть массивы с возможностью модификации элементов. А у компилятора имеется ключик, позволяющий отключить проверки на выход за границы массива. ;)


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

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

'a – это обозначение АБСТРАКТНОГО типа. Компилятор обозначит функцию make_list как функцию, которая принимает на вход значение абстрактного типа и возвращающую список значений данного абстрактного типа. Теперь попробуем применить эту функцию:

Выше функция make_list была применена два раза: сначала к целому значению, а потом к значению типа «строка». В первом случае функция вернула список из целых чисел. Во втором – список строк. Так что же произошло? По сути, когда мы описали функцию make_list, с точки зрения, скажем, C++ мы создали «шаблонную» функцию, которая при применении инстанцируется с заданным типом. Т.е. в данном случае наблюдается кардинальное различие между Caml'ом и C++ в подходе к написанию обобщенного кода. Если для C++ необходимо напрячься и явно написать обобщенную шаблонную функцию, то в случае с Caml'ом вообще не наблюдается различий в написании «просто» функций и «шаблонных» функций. Смог компилятор вывести все типы – получилась «простая» функция. Не смог компилятор вывести типы – получилась «шаблонная» функция. Более того, если вдруг программист взял и случайно, ни с того, ни с сего, написал обобщенный код, то компилятор САМ, фактически, создаст при этом шаблонную функцию. Вот такие дела...

Заключение

И функциях в O'Caml можно рассказывать еще очень долго. Тут и инфиксные функции, и функции высшего порядка, и замыкания... Но объем текста и так уже превысил все возможные пределы, поэтому, думаю, на сегодня хватит. :)


Ссылок на эту страницу нет


 
Файлов нет. [Показать файлы/форму]
Комментариев нет. [Показать комментарии/форму]