2009-12-20 4 views
8

Допустим, я создаю парсер для языка, специфичного для домена, в F #.F # design pattern

Я определил размеченное объединение для представления выражений:

type Expression = 
     | Equality of Expression*Expression 
     | NonEquality of Expression*Expression 
     | Or of Expression*Expression 
     | And of Expression*Expression 
     | If of Expression*Expression 
     | IfElse of Expression*Expression*Expression 
     | Bool of bool 
     | Variable of string 
     | StringLiteral of string 

Теперь я выстроил AST типа Expression и хочу сгенерировать код для него. У меня есть одна функция, которая делает вывод типа и тип проверки на выражение.

Он определяется как

let rec InferType expr = 
     match expr with 
     | Equality(e1,e2) -> CheckTypes (InferType e1) (InferType e2) 
     | Or(e1,e2) -> CheckTypes (InferType e1) (InferType e2) 
     | And(e1,e2) -> CheckTypes (InferType e1) (InferType e2) 
     ... 

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

Мой вопрос: Является ли это идиоматическим способом сделать это в F #?

Мне кажется, что было бы более чистым, если бы каждый член союза определил свои собственные InferType и GenerateCode локально с ним.

Если бы я использовал C#, я бы определил некоторый абстрактный базовый класс под названием Expression с виртуальными методами для InferType и GenerateCode, а затем переопределить их в каждом подклассе.

Есть ли другой способ сделать это?

ответ

17

Мне кажется, что было бы чище, если каждый член союза определил свою InferType и GenerateCode локально с ним.

Я считаю, что вы имеете в виду «более знакомый», а не «чище».

Действительно, это ваш идеальный вариант, чтобы реализовать генератор кода в 10 различных классах?

Существует определенно фундаментальное напряжение между тем, хотите ли вы группировать вещи «по типу» или «по операции». Обычный способ OO «по типу», тогда как способ FP (функциональное программирование) «работает».

В случае компилятора/интерпретатора (или большинства вещей, которые в OO сильно зависят от шаблона посетителя), я думаю, что «по операции» является более естественной группировкой. Генератор кода для If и And и Or может иметь несколько общего характера; у тестеров различных узлов также будут общие черты; если вы создадите симпатичный принтер, скорее всего, будут выполняться процедуры форматирования, общие для всех реализаций с довольно узкой печатью. Напротив, печать, проверка типов и кодирование IfElse действительно не имеют особого отношения друг к другу, так почему вы хотите сгруппировать их в классе IfElse?

(Чтобы ответить на ваши вопросы: да, это идиоматично. Есть ли другой способ - да, вы можете сделать это так же, как и на C#.Я думаю, вы обнаружите, что вы намного менее довольны C#, и что код будет также на 2-3 раза больше, без каких-либо преимуществ.)

+1

Спасибо - это ответ, который я искал , –

9

Как программист OCaml, я бы сказал, что это полностью идиоматично. Кстати, это дает вам лучшее разделение проблем, чем если бы вы написали иерархию классов с помощью методов класса. Вы получите аналогичную модульность на языке OO с посетителем InferType, но это будет намного больше кода.

1

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

let rec fold eqE nonE andE orE ... = function 
| Equality(e1,e2) -> eqE (e1 |> fold eqE nonE ...) (e2 |> fold eqE nonE ...) 
| NonEquality(e1,e2) -> nonE ... 
... 

let inferTypes = fold checkTypes checkTypes checkTypes ... 
+1

Является ли это 'fold' уродливым способом построить посетителя? Я по-прежнему предпочитаю стиль OP, где inferTypes определяется непосредственно оператором сопоставления шаблонов. – Tobu

+2

@Tobu: Я лично считаю его гораздо менее уродливым, чем шаблон посетителя. Как я уже сказал, в этом конкретном примере существует так много разных случаев, что я не думаю, что определение вещей с точки зрения складки лучше. В общем, однако, часто имеет смысл определять операции сложения по рекурсивным типам данных (например, встроенный в F_ 'List.fold'). – kvb