пятница, 13 июля 2012 г.

Проба пера на OCaml

Решил покурить OCaml. Первой (на самом деле нет) прогой на нём стал алгоритм Дейкстры преобразования выражений в польскую нотацию. Приводить алгоритм я здесь не буду, а вот ощущениями от языка поделюсь.
Итак, для начала сама прога лежит здесь в папке ocaml_my_example. Писалась она специально в функциональном стиле, поэтому стек здесь реализован через список, а не взят из стандартной библиотеки. Для проверки результата использовалась программа на C++, лежащая на википедии.

Исполнение программы начинается со строк
let line = read_line()
in let stringList = Str.split (Str.regexp "[ \t]+") line
in let tokenList = List.map string_of_token stringList
in List.map print_token (transformTokens tokenList);;
print_newline ();;


которые считывают строку, преобразуют её в список токенов и выводят результат работы алгоритма. Слово in позволяет ограничивать область видимости имени только для следующих объявлений, что весьма удобно.

Перед запуском алгоритма необходимо преобразовать строку в токены. Я сделал довольно примитивную функцию (делать сложную смысла нет), которая выглядит примерно так:
(*translating string into token*)
let    string_of_token = fun str ->
    match str with
    "+" -> Operator (new plus);
    | "-" -> Operator (new minus);
    | "*" -> Operator (new mult);
    | "/" -> Operator (new div);
    | "%" -> Operator (new mod_op);
    | "!" -> Operator (new not);
    | "=" -> Operator (new eq);
    | "," -> Comma;
    | "(" -> LBracket;
    | ")" -> RBracket;
    | _ as name when ((String.get name 0) >= 'A') && ((String.get name 0) <= 'Z') -> Operator (new func name);
    | _ -> Digit (int_of_string str);
;;


В ocaml часто вместо стандартной условной конструкции удобно использовать pattern matching, что видно из примера. В нём строка str проверяется на соответствии одному из шаблонов и в зависимости от этого создаётся новый объект. Шаблон _ означает любое значение (привет, prolog).

А теперь на радость плюсистам: объекты! Да, они здесь есть, есть член-функции и есть наследование. В общем сколько-нибудь инкапсуляцию и полиморфизм обеспечить возможно. Собсна, вот как это выглядит:
(*base class for all operators*)
class operator (assoc:association) (priority:int) (str:string) =
    object
        val assoc_ = assoc
        val priority_ = priority
        val string_ = str

        method assoc = assoc_
        method priority = priority_
        method to_string = string_
    end;;

(*operators itself*)
class eq = object inherit operator Right 1 "=" end;;
class plus = object inherit operator Left 2 "+" end;;
class minus = object inherit operator Left 2 "-" end;;
class mult = object inherit operator Left 3 "*" end;;
class div = object inherit operator Left 3 "/" end;;
class mod_op = object inherit operator Left 3 "%" end;;                (*because 'mod' is keyword*)
class not = object inherit operator Right 4 "!" end;;
class func name = object inherit operator Right 5 name end;;        (*when using a function you should give it name*)


Базовый класс operator, от которого наследуются уже конкретные операторы, причём сразу в описании можно задавать аргументы для конструктора. Кстати, члены по умолчанию immutable, что позволяет при работе с классами не нарушать функциональный стиль.

Для вызова метода класса вместо точки как в C++ используется решётка #. Например для печати оператора op нужно вызвать конструкцию  print_string op#to_string;.

Если внимательно посмотреть на объявление класса, можно заметить, что типы переменных в конструкторе заданы напрямую: class operator (assoc:association) (priority:int) (str:string). Это связано с тем, что по имеющейся информации о классе, а это только getter нельзя вывести тип члена. Хотя, казалось бы, зачем он здесь нужен?

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

Сам алгоритм вызывается строчкой (transformTokens tokenList). Занимает он ~45 строк против ~130 плюсового примера википедии. Вместо обхода списка циклом использована характерная для ФП хвостовая рекурсия, что пожалуй позволяет несколько лучше понимать каждый следующий шаг алгоритма.

В общем пока что мне OCaml больше нравится чем нет.

P.S. На самом деле пример из википедии не показатель, ибо написан он совершенно убого и даже не на C++.

Комментариев нет:

Отправить комментарий