无法重复使用计算表达式

时间:2017-02-24 05:28:48

标签: f#

我试图使用计算表达式来创建类似构建器的DSL,但是当我尝试使用let assignments来帮助编写内容时,我收到一个编译错误,指出无法找到这样的赋值。这是一个例子:

type Node = 
    {
        Key: Option<string>
        Children: List<Node>
        XPathFromParent: string
    }


let defaultNode = 
    {
        Key = None; 
        Children = [];
        XPathFromParent = ".//somePath"
    }


type NodeBuilder(xpath: string) =
    member self.Yield(item: 'a): Node =  defaultNode

    member this.xpath = xpath

    [<CustomOperation("xpath_from_parent")>]
    member __.XPathFromParent (node, x) = {node with XPathFromParent = x}

    [<CustomOperation("nodes")>]
    member __.Nodes (node, x) = {node with Children = x}

    [<CustomOperation("key")>]
    member __.MidasMeasurementKey (node, x) = {node with Key = x}

    member this.Bind(x, f) = f x


let node xpath = NodeBuilder(xpath)


let rootNode = node ".//somePath" {
    let! childNodes = 
        [
            node "somepath" {
                nodes []
            };

            node "someOtherPath" {
                nodes []
            }
        ]

    nodes childNodes  // The value or constructor 'childNodes' is not defined.
}

如何更改此代码,以便我可以引用childNodes作业将其传递到nodes自定义运算符?

1 个答案:

答案 0 :(得分:5)

您当前的问题是,您需要在自定义运算符的任何参数上放置[<ProjectionParameter>]属性,以便能够访问计算表达式的变量空间。但是,一旦你添加了这个,你会发现你有一些不匹配类型的问题。一般来说,我同意rmunn:计算表达式不一定适合你的问题,所以你应该强烈考虑使用不同的机制。

但是,如果你坚持推进,那么这是帮助你调试的一个技巧。看起来你想要写

node "something" {
    let! childNodes = ([some expression]:Node list)
    nodes childNodes
}

所以创建一个像这样的虚拟构建器(看似无用的Quote方法是关键):

type DummyNodeBuilder(xpath:string) = 
    [<CustomOperation("nodes")>]
    member __.Nodes (node:Node, [<ProjectionParameter>]x) = node // Note: ignore x for now and pass node through unchanged
    member __.Yield(_) = Unchecked.defaultof<_> // Note: don't constrain types at all
    member __.Bind(_,_) = Unchecked.defaultof<_> // Note: don't constrain types at all
    member __.Quote() = ()

let node xpath = DummyNodeBuilder xpath

let expr = 
    node "something" {
        let! childNodes = [] : Node list
        nodes childNodes
    }

你会看到expr的报价大致相当于:

builder.Nodes(
    builder.Bind([], 
                 fun childNodes -> builder.Yield childNodes),
    fun childNodes -> childNodes)

所以在您的真实构建器中,您需要具有兼容签名的方法(例如Nodes的第二个参数必须接受一个函数,并且第一个参数必须与返回类型{{1等等)。当您尝试使用虚拟构建器启用其他工作流时,您可以看到它们如何去除并发现其他约束。