f#代码生成的计算表达式
虽然我们可以找到一些关于如何用f#计算表达式进行组合递归下降解析器的例子,但我试图将它们用于相反的情况。 创建易于阅读的代码以从某些XML数据生成(c ++)源文件。 然而,我被困住了,如果社区能够帮助我发现我的误解,我将非常感激。 为了公共利益,我希望很快这篇文章将展示如何通过f#计算表达式,monadic风格以一种很酷的方式来生成代码生成器。
这是我到目前为止得到的结果(简化,为了这个问题省略了代的输入数据):
// 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#编程。
在我上面的代码示例中,我也无法看到从构建器monad中得到的东西。 与非一元实现相比,格式化程序代码看起来不够清晰。
如果有人在答复帖子中将参数和类型添加到参数中,那将会很棒; 至少对我来说,与“let-the-compiler-find-the-types”风格相比,它更容易理解。
好的,正如我在评论中提到的那样,这是一种功能风格的解决方案,我已经使用了一段时间并取得了很好的成功,尽管它不是纯粹的功能,它只是使用一些简单的函数而不是计算表达式。
首先,代码:从我的facio仓库抓取CodeGen.fs。 如果您想了解我在实践中如何使用这些功能,请查看FSharpLex / Backend.Fslex.fs和FSharpYacc / Backend.Fsyacc.fs。
所以,这是我实现代码生成的原因:
我在IndentedTextWriter
模块中定义的函数非常轻便,并且(IMO)易于使用。 如果您决定在自己的代码中使用我的函数,您可以放弃模块上的[<RequireQualifiedAccess>]
属性或将其更改为[<AutoOpen>]
以减少噪音。
与其实施一堆代码来管理缩进级别并将缩进字符串发送到底层的StringBuilder
,我更喜欢使用System.CodeDom.Compiler.IndentedTextWriter
因为它为您处理所有这些,并且它也是TextWriter
的一个实例,因此你可以使用fprintf
和fprintfn
等函数。
奖金: System.dll
包含IndentedTextWriter
,因为无论如何你几乎肯定会引用它,所以甚至不需要添加额外的引用来使用它!
IndentedTextWriter
只是包装TextWriter
另一个实例,所以你用它编写的代码(例如,在CodeGen.fs
使用我的函数)并没有绑定到特定的“目标”。 换句话说,您可以轻松修改它以写入StringBuilder(使用StringWriter
),磁盘上的文件(使用StreamWriter
)等等。
在你自己的代码中,你可以做这样的事情(只是为了给你一个想法):
let rec Block (formatters : Formatter list) (itw : IndentedTextWriter) =
itw.WriteLine "{"
IndentedTextWriter.indented itw <| fun itw ->
formatters |> List.iter (fun fmtr -> fmtr itw)
itw.WriteLine "}"
关于你的伪代码的另一个注意事项是:因为你的格式化状态是可变的(正如我的代码中的IndentedTextWriter
),所以实际上不需要将它从函数中传递出去 - 也就是说,通常只需要创建函数并返回当这些状态由不可变对象/值表示时的状态值。
奇怪的是,当传递一个可变作家(如我们的代码)时,你实际上需要“读者”工作流程或它的一些变体。 ExtCore在ExtCore.Control.Collections.Reader
模块中包含List,Array等的“阅读器”式函数,您可以使用它来进一步简化代码。