Search code examples
f#computation-expression

gettings attributes of f in a bind


I have a bit of code in a workflow where I'd like to get the attributes to a function

the expression is this

let! accounts = _accounts()

and in my bind I have this

member this.Bind(x,f) = 
    let attributes = 
      f.GetType()
       .GetCustomAttributes(typeof<myattribute>,false)

The idea is to get the attributes of the function _accounts(). However f represents the countinuation rather than _accounts and so anyway I can get the attributes of the called function?


Solution

  • I'd take one step back - first, you need to figure out what is the computation that you want to model. Based on what you said, you could keep a result with a list of some audit log information:

    type Audited<'T> = 
      { Log : string list
        Result : 'T }
    

    The standard basic computation builder would just create empty log in Return and Bind would simply concatenate the logs:

    type AuditBuilder() =
      member x.Return(v) = { Log = []; Result = v }
      member x.Bind(c, f) = 
        let fr = f c.Result
        { fr with Log = c.Log @ fr.Log }
    
    let audit = AuditBuilder()
    

    You could really just use this, provided that your accounts function would return a correct Audited<'T> value:

    let accounts () = 
      { Result = 40
        Log = ["accounts"] }
    let stocks () = 
      { Result = 2
        Log = ["stocks"] }
    
    audit {
      let! a = accounts()
      let! s = stocks()
      return a + s }
    

    Now, the question is, can we make this a bit nicer, so that accounts() does not have to be special function. You could do this in various ways - but it's more of a question about creating Audited<'T> values now!

    One way to do something like this would be to pass quotation to Bind. A very basic and simple implementation looks like this:

    let plain () = 123
    
    open Microsoft.FSharp.Quotations
    
    type AuditBuilder with
      member x.Bind(e:Expr<'T>, f:'T -> _) = 
        match e with
        | Patterns.Call(None, mi, []) -> 
            let r = f (mi.Invoke(null, [| |]) :?> 'T)
            { r with Log = r.Log @ [mi.Name] }
        | _ -> failwith "invalid"
    

    This adds an overloaded Bind that lets you "call" a quoted function, but it automatically extracts the name:

    audit {
      let! p = <@ plain() @>
      return p }
    

    This still needs the quotation - I guess you could experiment with other ways of doing this - but the key idea is that you have a basic computation, which really defines what the structure is.