I've looked around and struggled to get an answer to this; I'm sure there's an obvious answer but I just can't seem to find it; or I've hit a limitation of quotations that I can't pass when used with computation expressions.
Basically I want to work with an quoted lambda defined as below using a computation F# workflow. The problem comes when trying to compose these workflows together. Ideally I want to compose the Workflow<'Env, 'Result> instances together using the let! syntax. My somewhat naive attempt is below:
type Workflow<'Env, 'Result> = Expr<'Env -> 'Result>
type WorkflowSource<'Env, 'Result> = 'Env -> 'Result
type WorkflowBuilder() =
member x.Bind
(workflow: WorkflowSource<'Env, 'OldResult>,
selector: 'OldResult -> WorkflowSource<'Env, 'NewResult>) : WorkflowSource<'Env, 'NewResult> =
(fun env -> (selector (workflow env) env))
member x.Bind
(workflow: Workflow<'Env, 'OldResult>,
selector: 'OldResult -> WorkflowSource<'Env, 'NewResult>)
: Workflow<'Env, 'NewResult> =
<@ (fun env -> (selector ((%workflow) env) env)) @>
// This bind is where the trouble is
member x.Bind
(workflow: WorkflowSource<'Env, 'OldResult>,
selector: 'OldResult -> Workflow<'Env, 'NewResult>)
: Workflow<'Env, 'NewResult> =
<@ fun env ->
let newResultWorkflow = %(selector (workflow env))
newResultWorkflow env @>
member __.Return(x) = fun env -> x
member __.ReturnFrom(x : WorkflowSource<_, _>) = x
member __.Quote(x : Expr<WorkflowSource<_, _>>) : Workflow<_, _> = x
let workflow = new WorkflowBuilder()
The third bind member gives me the compiler error: "The variable "env" is bound in a quotation but used in a sliced expression" which kinda makes sense. The question is how do I get around it. I've defined the above as an attempt to try to get the simple below cases to work.
let getNumber (env: EnvironmentContext) = (new Random()).Next()
let workflow1 = workflow {
let! randomNumber = getNumber
let customValue = randomNumber * 10
return (globalId * customValue)
}
// From expression to non expression bind case
let workflow2a = workflow {
let! workflow1 = workflow1
let! randomNumber = getNumber
return (randomNumber + workflow1)
}
// From non-expression to expression bind case
let workflow2 = workflow {
let! randomNumber = getNumber
let! workflow1 = workflow1
return (randomNumber + workflow1)
}
Just wondering whether what I'm trying to achieve is possible or am I doing something wrong? Is it possible to get the above simple cases working while capturing the user functions inside the final quoted expression?
EDIT: I've also tried without the WorkflowSource type taking into account Tomas' answer. No luck still with error: System.InvalidOperationException: first class uses of '%' or '%%' are not permitted at Microsoft.FSharp.Core.ExtraTopLevelOperators.SpliceExpression[T](FSharpExpr`1 expression)
type WorkflowBuilder() =
member x.Bind
(workflow: Workflow<'Env, 'OldResult>,
selector: 'OldResult -> Workflow<'Env, 'NewResult>)
: Workflow<'Env, 'NewResult> =
fun env -> <@ %(selector (%(workflow env)) env) @>
member __.Return(x) = fun Env -> <@ x @>
member __.ReturnFrom(x: Workflow<_, _>) = x
member __.Quote(expr: Expr<Workflow<'Env, 'Result>>) = expr
// This run method fails
member __.Run(x : Expr<Workflow<'Env, 'Result>>) : Workflow<'Env, 'Result> = fun (env: Expr<'Env>) -> <@ %((%x) env) @>
let workflow = new WorkflowBuilder()
// Env of type int for testing
let getRandomNumber (kernel: Expr<int>) = <@ (new Random()).Next() @>
let workflow1 = workflow {
let! randomNumber = getRandomNumber
let otherValue = 2
let! randomNumber2 = getRandomNumber
return randomNumber + otherValue + randomNumber2
}
// This fails due to quotation slicing issue
workflow1 <@ 0 @>
This is just a rough sketch of an idea, but I think you can get further if you represent workflow not as a quoted function, but as a function that takes a quoted environment and returns a quoted result:
type Workflow<'Env, 'Result> = Expr<'Env> -> Expr<'Result>
Then you can certainly implement all of the binds:
member x.Bind
(workflow: WorkflowSource<'Env, 'OldResult>,
selector: 'OldResult -> WorkflowSource<'Env, 'NewResult>) : WorkflowSource<'Env, 'NewResult> =
(fun env -> (selector (workflow env) env))
member x.Bind
(workflow: Workflow<'Env, 'OldResult>,
selector: 'OldResult -> WorkflowSource<'Env, 'NewResult>)
: Workflow<'Env, 'NewResult> =
fun env -> <@ selector %(workflow env) %env @>
// This bind is where the trouble is
member x.Bind
(workflow: WorkflowSource<'Env, 'OldResult>,
selector: 'OldResult -> Workflow<'Env, 'NewResult>)
: Workflow<'Env, 'NewResult> =
fun env -> <@ %(selector (workflow %env) env) @>
That said, I think this is not quite all you need - it seems that the compiler is ignoring code in Quote
, so even if we add quote that turns WorkflowSource
into Workflow
, you still get errors because there are Expr<WorkflowSource<_>>
values - but I think another overload of bind might solve that.
member __.Quote(x : Expr<WorkflowSource<_, _>>) : Workflow<_, _> =
fun env -> <@ (%x) %env @>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With