4

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.

LSM07
  • 787
  • 2
  • 7
  • 21
robkuz
  • 9,488
  • 5
  • 29
  • 50

2 Answers2

4

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
}
Pierre Irrmann
  • 227
  • 1
  • 6
  • These particular arguments to the `CustomOperationAttribute` will allow access to the parameter of the computation builder too, no need to go via self-identifier and an additional property. – kaefer Mar 18 '19 at 10:47
  • Hey, what is the `into` thing? – robkuz Mar 18 '19 at 11:05
  • Also could you explain why the custom operation is needed to bring the builder itself into the scope? – robkuz Mar 18 '19 at 11:22
  • @kaefer could you elaborate? I don't see how you'd get access to the builder parameters. Maybe by providing a sample? – Pierre Irrmann Mar 18 '19 at 12:25
  • @robkuz The `into`thing was introduced in order to do the `query` computation expression ([see here](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/query-expressions)) which looks like the LINQ syntax from C#. The `into` keyword is used for things such `groupBy student.Age into g`. It allows you to bind the return value of the fonction to a name, so in that case I'm bending it to capture the builder itself... – Pierre Irrmann Mar 18 '19 at 12:31
3

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.

kaefer
  • 5,491
  • 1
  • 15
  • 20