F # Expressions for computing code generation

While you can find some examples of how to make composite recursive descent parsers with f # evaluation expressions, I tried to use them for the opposite. Create easy-to-read code to generate (C ++) source files from some XML data. However, I was stuck, and it would be very appreciated if the community helped me find my misunderstandings. Regarding the public good, I hope this post will soon demonstrate how to make code generators in cool mode using f # calculation expressions, monadic style.

Here's how far I'm still (simplified by omitting the input for Generation for this question):

// 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() 

Obviously, the code above only shows at best what I'm trying to achieve. My research did not give good examples of how to use computation expressions. Many of the examples that I found stopped showing how the developer was declared or a little later, but they could not show how to write the final expressions.

So, if someone takes the time to publish a real sample that does what my motto code above is trying to do, it would be very instructive to fill the gap in what you can find on the Internet about this (at least for me ) The confusing aspect of f # programming.

In my code example above, I also do not see what I get from the monad builder in the first place. The formatting code does not look cleaner than a non-monodic implementation.

It would be great if someone added signatures and types to parameters in response messages; at least for me this is much more understandable compared to the "let-the-compiler-find-the-types" style.

+4
source share
1 answer

OK, as I mentioned in the comments, this is a functional solution that I have used for some time with good success, although it is not purely functional, it just uses some simple functions instead of expressing a calculation.

Firstly, the code: grab CodeGen.fs from my facio repository. If you want to see how I used these functions in practice, look at FSharpLex / Backend.Fslex.fs and FSharpYacc / Backend.Fsyacc.fs .

So, here are my reasons for implementing my code generation as such:

  • The functions that I defined in the IndentedTextWriter module IndentedTextWriter very lightweight and (IMO) easy to use. If you decide to use my functions in your own code, you can discard the attribute [<RequireQualifiedAccess>] on the module or change it to [<AutoOpen>] to slightly reduce the noise.

  • Instead of injecting a bunch of code to control the indentation level and emitting indentation strings in the underlying StringBuilder , I prefer to use System.CodeDom.Compiler.IndentedTextWriter as it handles all this for you, as well as an instance of TextWriter , so you can use it with such functions like fprintf and fprintfn .

    Bonus: IndentedTextWriter included in System.dll , and since you will almost certainly refer to it anyway, you don’t even need to add an additional link to use it!

  • IndentedTextWriter simply wraps another instance of TextWriter , so the code you write with it (for example, using my functions in CodeGen.fs ) is not tied to a specific β€œtarget”. In other words, you can easily change it for writing to a StringBuilder (with StringWriter ), a file on disk (with StreamWriter ), etc.

In your own code, you can do something like this (just to give you an idea):

 let rec Block (formatters : Formatter list) (itw : IndentedTextWriter) = itw.WriteLine "{" IndentedTextWriter.indented itw <| fun itw -> formatters |> List.iter (fun fmtr -> fmtr itw) itw.WriteLine "}" 

One more note about your pseudo-code: because your formatting state is changed (like IndentedTextWriter in my code), there really is no need to pass it from your functions, i.e. you usually need to create functions that accept and return a state value when these states are presented immutable object / value.

Oddly enough, when you walk around a mutable writer (as in our code here), you really need a β€œreader” workflow or some version of it. ExtCore contains β€œreader” functions for list, array, etc. in ExtCore.Control.Collections.Reader module , which you can use to further simplify the code.

+4
source

Source: https://habr.com/ru/post/1488100/


All Articles