Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot re-use variable assigned within a computation expression

Tags:

f#

I'm trying to use computational expressions to create a builder-like DSL, but when I try to use let assignments to help compose things, I get a compilation error that such assignments cannot be found. Here's an example:

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.
}

How can I alter this code so that I can reference the childNodes assignment to pass it into the nodes custom operator?

like image 507
nebffa Avatar asked Sep 22 '26 07:09

nebffa


1 Answers

Your immediate problem is that you need to put a [<ProjectionParameter>] attribute on any arguments to custom operators that you wish to be able to access the variable space of the computation expression. However, once you add this, you'll find that you have some problems with mismatched types. In general, I agree with rmunn: computation expressions are not necessarily a good fit for your problem, so you should strongly consider using a different mechanism instead.

However, if you insist on pushing onwards, here's one trick to help you debug. It looks like you want to be able to write

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

So create a dummy builder like this (the seemingly useless Quote method is the key):

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
    }

and you'll see that expr holds a quotation roughly equivalent to:

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

so in your real builder you'll need to have methods that have compatible signatures (e.g. Nodes's second argument must accept a function, and the first argument must be compatible with the return type of Bind, etc.). As you try out other workflows you'd like to enable with the dummy builder, you can see how they desugar and discover additional constraints.

like image 189
kvb Avatar answered Sep 27 '26 12:09

kvb



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!