ООП в Окамле
Jun. 28th, 2010 09:36 pmСегодня впервые столкнулся с ситуацией, где использование ООП фич Окамла показалось уместным. Понадобилось по-разному трансформировать дерево программы в моем конпеляторе Leo. Например, нужно заменить переменные с заданными именами на заданные выражения. Или заменить везде один оператор на другой. В обоих случаях нужно аккуратно рекурсивно обойти все дерево и построить точно такое же, но в одном аспекте отличающееся. Дерево описывается набором ссылающихся друг на друга алгебраических типов - стейтменты, выражения, условия и т.д. Был бы это один тип, можно было бы следать универсальную функцию map, рекурсивно применяющую функцию-аргумент к узлам дерева, а так получается, что функций и аргументов понадобится много. Поэтому сделал иначе: объединил функции рекурсивного обхода в один класс, у которого можно переопределить нужные куски, остальное отдав на откуп унаследованной функциональности.
Например, замена переменных по переданному набору имя-значение выглядит так:
Тут создается объект неназванного типа, унаследованного от класса mapper, в нем переопределен один метод, а в нем фактически переопределен только один вариант АТД, все остальное делает базовый класс, рекурсивно проходя по всем закоулкам и вариантам дерева. Это можно еще упростить. Например, для трансформации стейтментов была добавлена такая функция:
Использование которой для совершения конкретной трансформации сводится вообще к одной строчке:
Кажется, без ООП так просто не получилось бы.
class mapper = object(self) method map_code code = List.map self#map_stmt code method map_stmt = function | Break | DefVar _ as x -> x | Assign(lv, rv) -> Assign(self#map_lvalue lv, self#map_rvalue rv) | Call(name, rvs) -> Call(name, List.map self#map_rvalue rvs) | Comp code -> Comp(self#map_code code) ... method map_lvalue = function ... method map_rvalue = function ... method map_cond = function ... end
Например, замена переменных по переданному набору имя-значение выглядит так:
let subst_vars smap code = let o = object inherit mapper as super method map_lvalue = function | Var name as x -> (try M.find name smap with Not_found -> x) | something_else -> super#map_lvalue something_else end in o#map_code code
Тут создается объект неназванного типа, унаследованного от класса mapper, в нем переопределен один метод, а в нем фактически переопределен только один вариант АТД, все остальное делает базовый класс, рекурсивно проходя по всем закоулкам и вариантам дерева. Это можно еще упростить. Например, для трансформации стейтментов была добавлена такая функция:
let subst_code_by_stmt f code = let o = object inherit mapper as super method map_stmt st = match f st with Some st' -> st' | None -> super#map_stmt st end in o#map_code code
Использование которой для совершения конкретной трансформации сводится вообще к одной строчке:
C.subst_code_by_stmt (function C.Print x -> Some(C.Prchar x) | _ -> None) code
Кажется, без ООП так просто не получилось бы.
no subject
Date: 2010-06-28 09:27 pm (UTC)У меня был достаточно извратский случай (по крайней мере, для кемла). Я делал игрушку с использованием чего-то, отдаленно напоминающего FRP. Были там всякие изменяемые во времени значения.
Так вот, этим значениям передавался неявный параметр -- состояние мира. Мне захотелось не хардкодить это состояние мира, а сделать его независимым от базовых комбинаторов.
В итоге у меня были
Все они жили в разных модулях и ничего не знали о том, кто и как их будет использовать. Но они знали, что в состоянии есть поля viewer/dt/peripherals.
При стыковке этих комбинаторов получались выражения с общим типом, например (< dt : Time.t; viewer : Viewer.t; .. >, Matrix.t) value.
В общем, жесть. В хаскеле такое делается обычными классами типов:
class HasDt simState where getDt :: simState -> DtПока писал коммент, понял, что сейчас я сделал бы все то же самое через функторы, которым передается ф-ия, выдирающая нужное подсостояние из общего состояния. Все равно не так удобно, как в хаскеле, но заметно лучше, чем с объектами -- очень уж часто < st : St.t; '_a .. > появлялись -- у функтора с фиксированным типом таких проблем будет поменьше.
Еще, в самом начале работы на кемле, я использовал объекты для графа сцены -- тоже глупость.
Собственно больше я нигде объекты и не использовал. Да и в этих двух случаях оказалось зря.
no subject
Date: 2010-06-29 02:37 am (UTC)