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.