vidi
vidi

Reputation: 2106

How to make a lazy computational workflow?

I'm trying to write a computational workflow which would allow a computation which can produce side effects like log or sleep and a return value

A usage example would be something like this

let add x y =
    compute {
        do! log (sprintf "add: x = %d, y= %d" x y) 
        do! sleep 1000
        let r = x + y
        do! log (sprintf "add: result= %d" r)
        return r
    }
...
let result = run (add 100 1000)

and I would like the side effects to be produced when executeComputation is called.

My attempt is

type Effect =    
    | Log of string
    | Sleep of int

type Computation<'t> = Computation of Lazy<'t * Effect list>

let private bind (u : 'u, effs : Effect list) 
         (f : 'u -> 'v * Effect list) 
         : ('v * Effect list) =
    let v, newEffs = f u
    let allEffects = List.append effs newEffs
    v, allEffects

type ComputeBuilder() =
    member this.Zero() = lazy ((), [])

    member this.Return(x) = x, []

    member this.ReturnFrom(Computation f) = f.Force()

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

    member this.Delay(funcToDelay) = funcToDelay

    member this.Run(funcToRun) = Computation (lazy funcToRun())

let compute = new ComputeBuilder()

let log msg = (), [Log msg]
let sleep ms = (), [Sleep ms]

let run (Computation x) = x.Force()

...but the compiler complains about the let! lines in the following code:

let x = 
    compute {
        let! a = add 10 20
        let! b = add 11 2000
        return a + b
        }

Error FS0001: This expression was expected to have type
    'a * Effect list
but here has type
    Computation<'b> (FS0001)

Any suggestions?

Upvotes: 3

Views: 167

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243106

The main thing that is not right with your definition is that some of the members of the computation builder use your Computation<'T> type and some of the other members use directly a pair of value and list of effects.

To make it type check, you need to be consistent. The following version uses Computation<'T> everywhere - have a look at the type signature of Bind, for example:

let private bind (Computation c) f : Computation<_> = 
    Computation(Lazy.Create(fun () ->
      let u, effs = c.Value
      let (Computation(c2)) = f u
      let v, newEffs = c2.Value
      let allEffects = List.append effs newEffs
      v, allEffects))

type ComputeBuilder() =
    member this.Zero() = Computation(lazy ((), []))
    member this.Return(x) = Computation(lazy (x, []))
    member this.ReturnFrom(c) = c
    member this.Bind(x, f) = bind x f
    member this.Delay(funcToDelay:_ -> Computation<_>) = 
      Computation(Lazy.Create(fun () -> 
        let (Computation(r)) = funcToDelay()
        r.Value))

Upvotes: 2

Related Questions