Шаблон дизайна F # - PullRequest
       21

Шаблон дизайна F #

8 голосов
/ 20 декабря 2009

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

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

    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, а затем переопределил их в каждом подклассе.

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

Ответы [ 3 ]

17 голосов
/ 20 декабря 2009

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

Полагаю, вы имеете в виду "более знакомый", а не "чище".

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

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

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

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

9 голосов
/ 20 декабря 2009

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

1 голос
/ 21 декабря 2009

Другая вещь, которую вы обычно можете сделать на функциональном языке, - это определить операцию 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 ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...