2013-06-25 33 views
4

虽然人们发现了一些关于如何用f#计算表达式进行组合递归下降解析器的例子,但我试图将它们用于相反的情况。创建易于阅读的代码以从某些XML数据生成(C++)源文件。然而,我被困住了,如果社区能够帮助我发现我的误解,我将非常感激。为了公共利益,我希望很快这篇文章将展示如何通过f#计算表达式,monadic风格以一种很酷的方式来生成代码生成器。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#编程方面。

在我上面的代码示例中,我也看不到我从构建器monad中得到的东西。与非一元实现相比,格式化程序代码看起来不够清晰。

这将是伟大的,如果有人添加签名和类型的答案职位的参数;至少对我来说,与“let-the-compiler-find-the-types”风格相比,它更容易理解。

+0

您是否对*不使用计算表达式的功能解决方案感兴趣?我在一些项目中使用了一个非常类似的东西,它非常简单易用。如果您愿意,我很乐意发布。 –

+0

@JackP。这将丰富讨论的肯定,并将非常欢迎!虽然它不会帮助我理解f#中的单点编程如何工作,但它会很好地匹配手头主题。 – user2173833

回答

4

OK,因为我在评论中提到的,这是我一直在使用具有良好的成功而函数式解决方案,但它不是纯粹官能的,它只是使用了一些简单的功能,而不是计算表达式。

首先,代码:抓取CodeGen.fs从我的facio存储库。如果您想了解我在实践中如何使用这些功能,请参阅FSharpLex/Backend.Fslex.fsFSharpYacc/Backend.Fsyacc.fs

所以,我这里还有我的原因,我的实现代码生成这样:

  • 我的IndentedTextWriter模块中定义的功能非常轻巧(IMO)易于使用。如果您决定在您自己的代码中使用我的功能,您可以放弃模块上的[<RequireQualifiedAccess>]属性或将其更改为[<AutoOpen>]以稍微降低噪音。

  • 无需实现一串代码管理缩进级别和发射压痕串到底层StringBuilder的,我更喜欢使用System.CodeDom.Compiler.IndentedTextWriter,因为它处理所有这一切对你来说,和它也是TextWriter一个实例,这样你可以使用fprintffprintfn等功能。

    奖励:IndentedTextWriter包含在System.dll中,因为无论如何你肯定会引用它,所以你甚至不需要添加额外的引用来使用它!

  • 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 module中的List,Array等的“阅读器”式函数,您可以使用它来进一步简化代码。

+0

感谢您的发帖!我试图做一个非一元语法分析器之前,有可变状态,我发现它不能很好地与序列的行为混合(我的输入是字符序列)。最后,当我的调试在意想不到的时刻触及令人惊讶的断点时,我取消了这种方法,当然,状态已经改变了。所以我想:我需要monads,因为它是不可预测的,否则f#控制流是什么。这就是为什么monads这次。希望能做到!产生可预测的控制流。我想我会遵循你的方法,除非一个f#Guru在这里展示,它应该如何“完成”:) – user2173833

+0

解析器非常难以编写,它是一个很好的例子,说明不可变状态如何简化你的代码。我上面展示的代码是*也是* monadic - 如果你愿意,你可以很容易地修改我的函数在工作流中工作。以这种方式编写代码生成应该给你一个非常可预测且易于调试的结果。 –

+0

几个星期过去了,重新访问这个尚未解决的问题,我强烈要求补充: monads应该帮助开发mini dsls。这样一个dsl的主要目的可能是从符号中隐藏上下文(例如作者),使其看起来像是C++源代码而不是writer.WriteLine(“...”)。我仍然不知道如何解决这个问题。 – user2173833