简体   繁体   中英

How to define Yield and For for custom computation operation in F#

I'm working on some DSL for my application and here's how I defined computation type and builder:

// expression type
type Action<'a,'b> = Action of ('a -> Async<'b>)

let runAction (Action r) ctx = r ctx
let returnF a = Action (fun _ -> async {return a})
let bind m f = Action (fun r -> async {
    let! a = runAction m r in return! runAction (f a) r
    })

let bindA ac f = Action (fun r -> async {
    let! a = ac in return! runAction (f a) r
    })

type ActionBuilder<'x>() =
  member this.Return(c) = returnF c
  member this.Zero()    = returnF ()
  member this.Delay(f)  = bind (returnF ()) f

  // binds both monadic and for async computations
  member this.Bind(m, f) = bind m f
  member this.Bind(m, f) = bindA m f

  member this.Combine(r1, r2) = bind r1 (fun () -> r2)
  member this.For(s:seq<_>, f)  = Action (fun x -> async {
    for i in s do runAction (f i) x |> ignore
    })

  // here's the attempt to implement 'need' operations

  [<CustomOperation("need")>]
  member this.Need(Action a,  targets: string list) =
    Action (fun x ->
      let r = a x
      printfn "need(%A, [%A])" a targets
      r)

   member this.For(a, f)  = bindA a f
   member this.Yield(()) =
    returnF ()

let action = ActionBuilder<string>()

/////////////////////////////////////////////////////////////
// other functions for Action

/// Gets action context
let getCtx = Action (fun ctx -> async {return ctx})


let needFn res = action {
    let! ctx = getCtx
    printfn "need([%A]) in %A" res ctx
  }

The resulting code is supposed to be:

let program1 = fun filename -> action {
  let! a = async {return 123}
  let f = a+1

  // need ["def"; "dd"]
  do! needFn ["def"; "dd"]
  printfn "after need"

  for i in [0..10] do
    do! Async.Sleep (1)
    printfn "i: %A" i

  let! d = async {return f}
  let! ctx = getCtx
  printfn "ctx: %A, %A" ctx f
}

Async.RunSynchronously(runAction (program1 "m.c") "abc")

Now I would like to change do! needFn ["def"; "dd"] do! needFn ["def"; "dd"] do! needFn ["def"; "dd"] syntax to a nicer one by defining "need" custom operation , but getting various complains from compiler. Is it correct approach or I'm misusing the computation expressions?

The other issue is that for does not work if do! is used inside loop body.

After reading papers, by trial and error method I came to the following for implementation ( Yield builder method is not required):

    let forF (e: seq<_>) prog =
    usingF (e.GetEnumerator()) (fun e ->
        whileF
            (fun () -> e.MoveNext())
            ((fun () -> prog e.Current) |> delayF)
    )

Full source code for computation expression builder could be found in the target project . The whole project is a variation of Fake build system.

Note: Action was renamed to Recipe. need operator cannot be implemented at all.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM