включить редактирование

Знакомство с OCaml

Создан:

OCaml (первоначально "Objective Caml" — объектно-ориентированный Calm) — основная реализация языка Caml, который, в свою очередь, является наследником языка ML родом из 1970-х, предка многих других языков. Как и ML, Caml является функциональным языком, и он статически типизирован, что здорово улучшает производительность и позволяет избежать определенного набора ошибок. OCaml добавляет в язык несколько возможностей, в том числе объектную ориентированность, объединяя функциональное, императивное и объектно-ориентированное программирование, что дает очень гибкий конечный результат. Он также повышает быстродействие, оптимизируя компилятор и предоставляя особенно хорошие реализации определенных функций (например, набор union); а благодаря его библиотечной природе программы легко компилировать.
Изучение OCaml позволяет усвоить основные идеи функционального программирования, которые также проявляются в других функциональных языках вроде Haskell, что улучшает общий уровень программирования. Кроме того, функциональные шаблоны очень полезны при решении современных задач программирования, таких как обработка параллелизма. И вообще, OCaml — прекрасная рабочая лошадка: при желании на нем можно даже писать скрипты. Вот и начнем.
Теперь у OCaml есть собственный менеджер пакетов, OPAM, который хорошо работает в Linux. С его помощью устанавливать и управлять OCaml и его библиотеками удобнее всего. OCaml так же можно установить через менеджер пакетов дистрибутива (ocaml — базовый пакет с графикой для Debian и Fedora).

 wget http://www.ocamlpro.com/pub/opam_installer.sh
sh ./opam_installer.sh /usr/local/bin
eval `opam config env`

При желании можно, разумеется, установить его не в /usr/local/bin, а в другой каталог. Для использования ocamlc последнюю строку нужно добавить в .bashrc, или выполнять эту команду каждый раз при запуске нового терминала. После установки можно попробовать запустить свою первую программу на OCaml в интерактивной командной строке OCaml. Наберите ocaml, чтобы открыть командную строку, и затем следующее:
 # print_endline “Hello World”;;
Hello World
_ : unit = ()
# exit 0;;

Команды верхнего уровня в OCaml заканчиваются двойной точкой с запятой (;;), а аргументы функций OCaml, как обычно бывает в функциональных языках, не ограничиваются скобками. Для выхода из интерпретатора укажите в функции exit код возврата в качестве аргумента (в данном случае, ноль, означающий успешное завершение программы).
Наряду с выполнением команды, интерпретатор так же сообщает результат и тип результата. В данном случае, результат вызова print_endline — это массив (unit), единственное значение которого — (). Эта конструкция напоминает функцию void в C и используется, когда возвращаемое значение не важно. В данном случае оно не важно потому, что главная задача функции — это вывод строки, а не возврат результата.
Программы можно набирать в интерактивной командной строке, но вы скорее всего будете записывать их в файлы. Создайте файл hello.ml:
print_endline “Hello World”;; и запустите его командой ocaml hello.ml. Для компиляции программы воспользуйтесь командой
ocamlc -o hello hello.ml
(после чего запустите программу командой ./hello). Как правило, код компилируется и запускается таким образом почти всегда. Скомпилированный код работает быстрее; при хранении программы в файлах ее легко разбить на несколько файлов; а вдобавок при этом проще использовать библиотеки, которые не включены в ядро OCaml.
В OCamI используются двойная точка с запятой (;;) или одна точка с запятой (;), а иногда и ни одной. Вот когда используется каждый из вариантов:
  • ;; используется для разделения команд верхнего уровня в коде.
  • ;; НЕ используется внутри определений функций и внутри команд.
  • Иногда;; можно пропустить — в частности, перед let, open, type и в конце файла. Во всех этих случаях OCamI может уверенно определить, что далее следует новое выражение.
    Делать это необязательно (и, возможно, лучше уж и не делать — программа будет читабельнее), но такое вам может встретиться в коде, написанном другими.
  • ; фактически представляет собой оператор this; that означает «получить два аргумента this и that и вернуть второй (that)». В процессе оба аргумента будут вычислены. На практике этот оператор используется для разделения команд в блоках кода.
  • let... in представляет собой единую команду, и после нее (;) указывать не нужно.
  • Все остальные команды, кроме последних в блоках кода, должны заканчиваться (;).
В кодах статьи вы увидите все эти правила в действии. Некоторые операторы (;;) можно было опустить, но для ясности я их оставил.

Модули.
Каждый фрагмент кода OCaml входит в какой-то модуль. Код построения графика, который мы будем писать в этом модуле в файле mygraph.ml, является частью автоматически созданного модуля Mygraph. Поэтому в файле можно вызывать любые функции, например, Mygraph.gtkwindow (). Как будет обсуждаться далее в этой статье, если нужно вызвать функцию gtkwindow, не указывая имя модуля, можно просто поместить в начало файла строку open Mygraph. Это следует делать только если имена функций в модуле не совпадают с именами других используемых функций: в противном случае все равно придется указать полное имя. (Например, имена функций модуля List часто используются в других модулях, поэтому обычно никто не указывает в начале модуля open List.) Базовая библиотека OCaml очень мала и содержит лишь простейшие типы и исключения, а также модуль Pervasives с операциями над ними. Стандартная библиотека (связываемая с программой автоматически — в программе ее указывать не надо) чуть больше: там массивы, карты, списки и операции вывода. Все прочее надо устанавливать (обычно командой opam) и явно привязывать в командной строке, как далее в коде в данной статье.

Рисуем график.
Далее в статье мы напишем программу для рисования графика с помощью нескольких графических библиотек OCaml. В OCaml есть масса очень надежных библиотек, и ими нужно пользоваться при любой возможности. Во-первых, попробуем базовую графическую библиотеку — нарисуем несколько кружков:
open Graphics;;
open_graph" 640x480";;
for i = 12 downto 1 do
let radius = 20 in
set_color blue;
fill_circle (i * 30) (i * 25) radius
done;;
read_ine ();;

Для этого нужно открыть библиотеку Graphics. Она находится в файле graphics.cma - имена пакетов в OCaml пишутся в нижнем регистре, но в обращении их название указывается с большой буквы. open_graph — это функция из библиотеки. Здесь она подключается к экрану по умолчанию и создает окно размерами 640x480. При желании можно уточнить экран и расположение окна; более подробная информация указана в документации. Стандартные координаты — математические: начало координат находится в левом нижнем углу окна, у увеличивается вверх, а х—вправо.
В OCaml есть две версии цикла for. В этой, for... downto...do... done;; значение переменной цикла уменьшается с каждым проходом цикла. Чтобы оно увеличивалось, используйте to вместо downto и укажите начальное и конечное значения переменной. Тело цикла находится между do и done;;.

Очень распространенная идиома в коде на Ocaml — let имя-переменной = выражение in. Она определяет переменную с текущей областью видимости. Так, здесь переменная radius определяется только для оставшейся части блока кода (до следующих;;, т. е. для конца цикла for). Но по умолчанию значение переменной в OCaml нельзя поменять или присвоить другой переменной: переменные являются неизменяемыми. В функциональном программировании это нормально. В OCaml, как в императивном языке, есть и изменяемые переменные — их мы рассмотрим в следующем разделе, но большую часть времени они здесь нам не потребуются. Неизменяемость переменных позволяет избежать ошибок определенного типа.
Наконец, set_color и fill_circle — другие функции из библиотеки Graphics, которые делают именно то, что следует из названия [Назначить цвет и Закрасить круг]. Попробуйте draw_circle [Нарисовать круг] и поглядите, что произойдет, read_ine ожидает ввода из стандартного потока ввода; эта команда нужна для того, чтобы график оставался на экране. Если ее не будет, программа закончится, и график сразу же исчезнет. Чтобы скомпилировать этот код, нужно сослаться на графическую библиотеку:
ocamlc graphics.cma mygraph.ml -о mygraph
Скомпилируйте и запустите программу, чтобы увидеть набор кружков. Вместо рисования круга с заданной шириной дадим пользователю кое-что ввести и нарисуем график.
use Graphics;;
print_endline "Enter value a (integer)";;
let a = int_of_string(read_line ());;
print_endline "Enter value b (integer)";;
let b = int_of_string(read_line ());;
open_graph" 640x480";;
for x = 0 to 639 do
set_color red;
let y=(a*x)+b in
plot х у
done;;
read_line ();;

Сначала мы спрашиваем у пользователя несколько чисел, используя функцию read_line () по прямому назначению: считывать данные из стандартного потока ввода (с клавиатуры). Потом read_line () возвращает строки, а строк OCaml автоматически не преобразует; надо превратить нашу строку в целое число с помощью функции int_of_string() (для преобразования данных между базовыми типами также есть функции float_of_string(), string_of_int()). Затем мы открываем графическое окно и рисуем точки графика. Цикл for перебирает все значения х (для окна в 640 пикселей высотой), и для каждого из них задается цвет, определяется у как ах+b и выводится на экран точка (х, у). Так как let у — в цикле for, у существует только внутри этой итерации цикла; у можно снова объявить в следующем цикле, и к ней не надо обращаться: ее значение никогда не меняется, каждый раз она объявляется снова. Следующая строка, read_line (), опять же требуется исключительно для того, чтобы график остался на экране и мы могли его рассмотреть.
ocaml_1.jpg

Определение функций.
OCaml — функциональный язык программирования; но как же определить свою собственную функцию? Перепишем код построения графика как функцию и одновременно сделаем так, чтобы она принимала на вход вещественные числа (подробнее о которых — далее):
let а = float_of_string(read_line ());;
let b = float_of_string(read_line ());;
let draw_graph = fun ()->
for x = 0 to 639 do
set_color red;
let у = (a *. float_of_int(x)) +. b in
plot x (int_of_float(y))
done;
;;
open_graph " 640x480";;
draw_graph ();;
read_line ();;

На сей раз мы превратили строку в вещественное число (float) вместо целого (int). Затем с помощью let мы присваиваем метку функции. Конструкция fun параметры -> [тело функции];; определяет функцию. В данном случае параметров у функции нет, поэтому мы для ясности указываем ().
Тело функции почти не отличается от предыдущего цикла for, за исключением того, что в качестве исходных значений и для вычисления используются числа с плавающей точкой; тогда как методу plot х у нужны целые числа, х создается в цикле for как целое число, поэтому при вычислении у через а и Ь (оба числа вещественные), мы должны преобразовать х в вещественное число методом float_of_int. При этом сама переменная х остается целой, мы просто создаем по ней временную вещественную величину. Так что в следующей строке можно использовать х в качестве параметра функции plot, а вот вещественную переменную у нужно преобразовать в целую величину.
Вы также заметите, что мы используем *. и +. вместо * и +. В OCaml арифметические операции (+-*/) применяются только к целым числам и не перегружаются. Если нужно использовать эти операции с числами других типов (или с объектами любых других типов), вместо них нужно использовать +. -.*./..
Наконец, мы вызываем различные функции. Функцию draw_graph можно немного обобщить, заставив ее принимать параметры:
let draw_graph = fun a b -> 
for х = 0 to 639 do
set_color red;
let у = (а *. float_of_int(x)) +. b in
plot x (int_of_float(y))
done;
draw_graph ab;;

Все, что нам нужно — объявить переменные сразу после fun. OCaml сам определит их типы. Возможно, вы заметили, что я перезаписал глобальные значения а и b в локальной функции draw_graph. Хотя в этом нет ничего страшного, для ясности можно было бы назвать переменные иначе.
Было бы здорово перенести в функцию и ввод данных пользователем, но для этого нам потребуется настоящая переменная: та, значение которой можно изменять и присваивать в любой момент. Пока у нас их не было — я лишь присваивал переменным имена оператором let. let сам по себе не создает переменную, он лишь присваивает чему-либо ярлык. Присвоить значение переменной, объявленной с помощью let, нельзя — на самом деле это совсем не переменная. Для создания обычной переменной воспользуемся ключевым словом ref:
let user_a = ref 0.0
let user_b = ref 0.0
let get_user_input = fun () ->
(* Output user request information to screen before getting input")
user_a := float_of_string(read_line ());
user_b := float_of_string(read_line ())
;;
get_user_input ();;
open_graph 640x480";;
draw_graph !user_a !user_b;;
read_line ();;

Мы все равно воспользуемся let для создания меток (user_a и user_b). Однако когда мы используем ref 0.0 вместо присваивания метки переменной, мы присваиваем ссылке метку. Ссылка напоминает указатель в С; теперь метка указывает на то, что хранится в определенной ячейке памяти.
Для присваивания ссылки нужно пользоваться оператором :=, а не просто =, так как user_a — просто адрес ячейки памяти, а нам нужно изменить содержимое ячейки. Для получения значения ссылки (ее разрешения) используется конструкция !user_a и !user_b. Опять же, если вы сталкивались с любым кодом на С или пользовались указателями в других языках, такой синтаксис будет вам знаком.
Также обратите внимание на то, что последняя строка get_ user_input не заканчивается точкой с запятой, и каждая из двух строк let в начале файла не заканчивается оператором (;;). Точку с запятой можно безопасно опускать в последней строке функции или в конце строки, если следующая начинается с let, open или type.
ocaml_2.jpg

Использование GTK
Теперь вместо ввода данных пользователя с клавиатуры мы нарисуем графическое окно для обработки этой информации с помощью библиотеки GTK. Библиотека GTK в OCaml — lablgtk — очень неплоха. Установите ее в ОРАМ командой opam install lablgtk. Сначала создадим наше базовое окно в новом файле mygraph2.ml:
open GMain
open GdkKeysyms
let gtkwindow () =
let window = GWindow.window ~width:320 ~height:240 ~title:"values" () in
let vbox = GPack.vbox ~packing:window#add () in
window#connect#destroy ~callback:Main.quit;
let entry_a_label = GMisc.label ~packing:vbox#pack () in
entry_a_label#set_text "Enter value a below";
let entry_a = GEdit.entry ~max_length:10 ~packing:vbox#pack О in
entry_a#connect#activate ~callback:(get_value_a entry_a);
entry_a#connect#changed ~callback:(get_value_a entry_a);
let entry_b_label = GMisc.label ~packing:vbox#pack () in
entry_b_label#set_text "Enter value b below";
let entry_b = GEdit.entry ~max_length:10 ~packing:vbox#pack О in
entry_b#connect#activate ~callback:(get_value_b entry_b);
entry_b#connect#changed ~callback:(get_value_b entry_b);
let button = GButton.button ~label: "Draw graph!" ~packing:vbox#add () in
button#connect#clicked ~callback:(Main.quit);
window#show ();
Main.main ()
;;
gtkwindow ();;
open_graph "640x480";;
draw_graph !user_a !user_b;;
read_line ();;

Вы видите, что здесь появилось две новых формы обозначений: # и ~.
С помощью # в OCaml реализовано объектно-ориентированное программирование. entry#connect#activate аналогично entry.connect().activate() в Java. Берется объект entry, к нему применяется функция connect, и ко всему этому применяется функция activate.
Зачем нужна ~? Она позволяет задавать метки для аргументов. Так, в строке
let window = GWindow.window ~width:320 ~height:240 ~title:"values" ()
мы передаем аргументы width, height и title методу window в модуле GWindow. Эти метки означают, что вам не нужно беспокоиться о порядке аргументов; так мы улучшим читаемость кода.
Аналогично в этой строке передаем аргумент callback: entry_a#connect#activate ~callback:(get_value_a entry_a); Это функция обратного вызова для activate.

Мы создаем окно GTK window и вертикальное окно vbox, которое помещается внутрь первого окна; и мы назначаем окну функцию обратного вызова, чтобы при закрытии окна завершился главный (Main) цикл GTK. Главный цикл GTK реализует все аспекты жизненного цикла окна GTK.
После этого мы создаем метку и задаем ее текст, а также создаем GEdit (текстовое поле) для value а и value b. В окне также есть кнопка Draw graph [Нарисовать график]. У полей и у кнопки есть функции обратного вызова, когда их работа закончена, и мы рассмотрим их далее. Наконец, мы отображаем окно на экране и передаем управлением и главному циклу GTK.
Параметр callback — это функция, вызываемая при возникновении определенных событий. Так, для текстовых полей мы устанавливаем одну функцию обратного вызова для активации поля (когда пользователь нажимает Enter) и одну для изменения поля (любое нажатие клавиши в поле). Если вместо нажатия Enter пользователь щелкнет мышью вне поля, мы пользуемся changed, activate в этом случае не сбрасывается. Для кнопки мы просто вызываем Main.quit — выход при нажатии кнопки.
Это означает, что код завершит функцию gtkwindow и перейдет к следующей вызываемой функции (open_graph и draw_graph).
Поэтому следующее, что нужно сделать — задать функции обратного вызова.
let get_value_a entry () =
let text = entry#text in
user_a := float_of_string(text);
(* prerr_endline text *)
;;

В качестве параметра мы передаем entry (поле ввода). Напомню, что структура функции обратного вызова такова:
~callback:(get_value_a entry_a);

В ней определяется функция, которая будет вызвана, и данные, которые будут ей переданы. С помощью entry#text мы получаем строку из поля ввода, которую затем преобразуем в вещественное число и присваиваем переменной user_a. Закомментированная строка выводит сообщение в стандартный поток ошибок, что удобно при отладке, но не слишком нужно в готовой программе.
Функция get_value_b почти идентична предыдущей, но присваивает значение переменной user_b. Можно ли как-то обобщить код? Оказывается, можно:
let gtkwindow () = 
(* as before *)
entry_a#connect#activate ~callback:(get_value user_a entry_a);
entry_a#connect#changed ~callback:(get_value user_a entry_a);
(* same change for entry_b; rest as before *)
;;
let get_value value entry () =
let text = entry#text in
value := float_of_string(text);

Код не требует никаких пояснений. Единственное, что следует отметить — user_a надо передавать как ссылку, т.е. без!, разрешающего ее значение. После этого мы просто присваиваем ей значение в функции get_value. Для компиляции этой программы воспользуемся командой:
ocamlc -l +../lablgtk2 lablgtk.cma graphics.cma gtklnit.cmo -о mygraph mygraph.ml

Теперь запустите программу и полюбуйтесь своим новым интерфейсом.
Конечно, здесь я рассказал только о самых основах языка программирования OCaml, и можно изучить еще массу вещей. Как в функциональном языке, в OCaml есть замкнутые функции (содержащие в себе часть своего окружения), на которые стоит обратить внимание, а также частичные функции. В документации по OCaml не все отражено полно, есть масса полезных руководств на сайте http://ocaml.org.

Автор: vj0R Просмотров: 5267


Рейтинг статьи: 0

Общий рейтинг из всех статей автора :
{0 [1]} [ - - - - - - - - - - ]

Общий рейтинг из всех статей на сайте :
{0 [888]} [ - - - - - - - - - - ]

[?]
комментариев к данной статье нет

Добавить комментарий к статье


Ctrl+Enter

Для активации кнопки, введите символы, которые Вы видите на картинки.

новая

тема

Заметки на тему IT

Монитор поиска
[x]
Новое сообщение

Сообщения в чате

Вы спрашиваете у гостей/у зарегистрированных/ У Вас спрашивают
всем Ctrl+Enter
зарегистрированным Ctrl+Enter
Ctrl+Enter

Краткая инструкция по работе с чатом

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