【发布时间】:2013-06-25 14:17:14
【问题描述】:
虽然人们找到了一些关于如何使用 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# 编程的令人困惑的方面。
在我上面的代码示例中,我也看不到我首先从 builder monad 中得到了什么。与非单子实现相比,格式化程序代码看起来并不干净。
如果有人在答案帖子中将签名和类型添加到参数中,那就太好了;至少对我来说,与“让编译器查找类型”的风格相比,它更容易理解。
【问题讨论】:
-
您对不使用计算表达式的函数式解决方案感兴趣吗?我有一个我在几个项目中使用过的非常相似的东西,它非常简单易用。如果您愿意,我很乐意发布。
-
@JackP。这肯定会丰富讨论,并且非常受欢迎!虽然它不能帮助我理解 f# 中的一元编程是如何工作的,但它会很好地匹配手头的主题。
标签: f# code-generation computation-expression