Хотя можно найти несколько примеров того, как выполнять синтаксические анализаторы композиционного рекурсивного спуска с выражениями вычисления f #, я попытался использовать их для противоположного. Чтобы создать легко читаемый код для генерации (c ++) исходных файлов из некоторых данных XML. Тем не менее, я застрял, и было бы очень признательно, если бы сообщество могло помочь мне найти мои недопонимания. Для всеобщего блага я надеюсь, что вскоре в этом посте будет продемонстрировано, как создавать генераторы кода классным способом с помощью выражений вычисления f # в монадическом стиле.
Вот как далеко я продвинулся (упрощенно, опуская входные данные для поколения для целей этого вопроса):
// in my full fledged application, State type also contains the Input data, used for generating code.
type State() =
let builder = new System.Text.StringBuilder()
let mutable indentLevel : int = 0
member this.Result() = builder.ToString()
member this.Emit (s : string) : unit = builder.Append( s )
// ... Methods allowing to do the indenting right, using indentLevel. And adding Output to the builder instance.
member this.Indent() = indentLevel <- indentLevel + 1
member this.Exdent() = indentLevel <- indentLevel - 1
// The return value of the Formatters is State only to allow for |> pipelining.
type Formatter = State -> State
type FormatterBuilder() =
// Q: Bind() Kind of Looks wrong - should it be a generic, taking one generic first Parameter? See Class function below.
member this.Bind (state,formatter) = formatter state
member this.Return state = state // Q: Not sure if this is the way to go. Maybe some Lambda here?!
let format = new FormatterBuilder()
// Q: Now Comes the part I am stuck in!
// I had the idea to have a "Block" function which
// outputs the "{", increases the indent Level,
// invokes the formatters for the Content of the block,
// then reduces the indent Level, then Closes "}".
// But I have no idea how to write this.
// Here my feeble attempt, not even sure which Parameters this function should take.
let rec Block (formatters : Formatter list) (state : State) : State =
format
{
state.EmitLine("{") // do I Need a "do!" here?
state.Indent()
formatters |> List.iter (fun f -> do! f state) // Q: "state" is not really propagated. How to do this better?
state.Exdent()
state.EmitLine "}"
}
// Functions with "Get" prefix are not shown here. They are supposed to get the Information
// from the Input, stored in State class, which is also not shown here.
let rec Namespace (state : State) : State =
format
{
state.EmitLine(GetNameSpace state)
}
let rec Class (classNode : XmlNode) (state : State) : State =
Format
{
do! TemplateDecl classNode state // TemplateDecl function not shown in sample code
do! ClassDecl classNode state
do! Block [ NestedTypes classNode; Variables classNode; // ... ] // just to give the idea. Q: the list seems wrong here - how to do it better?
}
let GenerateCode() : string =
let state = new State()
format
{
do! Namespace state // Q: Is there a way to get rid of the passing of state here?
do! Block
[ // Q: Maybe a Seq is better than a list here?
for c in State.Classes do // Q: requires override of a few functions in Builder class, I guess?!
do! Class c state
]
}
state.Result()
Очевидно, что приведенный выше код в лучшем случае показывает только то, чего я пытаюсь достичь. Мое исследование не дало хороших примеров того, как использовать вычислительные выражения. Многие примеры, которые я нашел, останавливаются на том, чтобы показать, как объявляется построитель, или немного позже, но не демонстрируют, как на самом деле писать окончательные выражения.
Итак, если кто-то найдет время опубликовать реальный образец, который делает то, что пытается сделать мой бредовый код выше, это будет очень поучительно и заполнит пробел в том, что можно найти в Интернете в отношении этого (по крайней мере, для меня) сбивающего с толку аспекта. программирования на f #.
В моем примере кода выше я также не вижу, что я получаю от монады построителя. Код средства форматирования не выглядит чище по сравнению с немонадической реализацией.
Было бы здорово, если бы кто-нибудь добавлял подписи и типы в Параметры в ответных сообщениях; по крайней мере, для меня это намного более понятно по сравнению со стилем «пусть компилятор найдет типы».