robkuz
robkuz

Reputation: 9914

How to Access a Value from a Builder using Custom Operation in a Computation Expression

I have a Computational Expression Builder which receives a value during construction

type SomeBuilder<'e> (e: 'e) =
    member this.Bind(x, fn) = ...
    member this.Return x  = ...
    member this.ReturnFrom x = ...

let buildSome v = SomeBuilder(v)

buildSome 2 {
    return 1
}

Now I'd like to access the value e from within the Computational Expression via a custom operation so that

buildSome 2 {
    return 1 + e()
}

So I really want to access properties/values in the underlying builder object and work with them

I imagine I would need something like

type SomeBuilder<'e> (e: 'e) =
    member this.Bind(x, fn) = ...
    member this.Return x  = ...
    member this.ReturnFrom x = ...
    [<CustomOperation("e")>]
    member this.E () = e        

but that doesn't work.

So my question is

a) is something like this possible using CustomOperations and Computational Expressions b) and if it is possible, how?

Disclaimer:
As usual in programming there is a million ways to achieve similar effects in completely different ways. I am explicitly asking for this particular way and I am OK if the answer is simply "No". But please refrain from answers that are non answers in the narrowest sense laid out here.

Upvotes: 4

Views: 235

Answers (2)

kaefer
kaefer

Reputation: 5741

To show that primary constructor arguments are in scope for the builder's instance methods:

type SomeBuilder<'e> (e: 'e) =
    member __.Bind(x, fn) = fn x
    member __.Return x = x
    [<CustomOperation("e", MaintainsVariableSpaceUsingBind = true,  AllowIntoPattern = true)>]
    member __.E _ = e

SomeBuilder 2 {
    e into i
    return 1 + i }
// val it : int = 3
SomeBuilder "bar" {
    e into s
    return "foo" + s }
// val it : string = "foobar"

Consider the position of the custom operation inside the builder; it will ignore expressions that precede it.

Upvotes: 3

Pierre Irrmann
Pierre Irrmann

Reputation: 227

I'm not sure you'll like my answer and whether it's within your boundaries, but you could capture the builder instance using a trick like this:

type SomeBuilder<'e> (e: 'e) =
    member this.Value = e

    [<CustomOperation("extract", MaintainsVariableSpaceUsingBind = true, AllowIntoPattern = true)>]
    member this.Extract (state) = this

    member this.Bind(x, fn) = fn x
    member this.Return x  = x
    member this.ReturnFrom x = x


let builder e = new SomeBuilder<_>(e)

let x = builder 1 {
    extract into builder // now we've brought builder in the scope
    printfn "here we can read the value = %d" builder.Value
    return 0
}

Upvotes: 4

Related Questions