Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how do i create a computational expression that takes parameters?

Tags:

monads

f#

I want to create a couple of computational expressions that would be used to access the database and return a list of items like so (I also have questions in the code comments):

let foo x y z = proc "foo" {
    let! cmd = proc.CreateCommand() // can I do this?
    do! In "x" DbType.Int32 // would i gain anything by replacing DbType with a union 
                            // type since the names would match actual data types?
    do! In "y" DbType.String 15;
    cmd?x <- x
    cmd?y <- y
    use! r = cmd.ExecuteReader() // would this be bad form for creating a workflow builder?
    return! r {
        let item = MyItem()
        do! item.a <- r.GetInt32("a")
        do! item.a <- r.GetString("b")
        do! item.c <- r.GetDateTime("c")
        yield! item
    }
}

How can I create a workflow builder such that an instance of it takes a parameter?

let proc name = ProcedureBuilder(connStr, factory) // how do I do this?
like image 519
Charles Lambert Avatar asked Jan 20 '23 18:01

Charles Lambert


2 Answers

Yes, you can do this. You can use computation expression syntax after any expression with a type statically known to expose the right methods. So the following code works (but doesn't do anything particularly interesting):

let f x = async
let v = f "test" { return 1 }

Here, f has type 'a -> AsyncBuilder, so f "test" has type AsyncBuilder and can be followed with computation expression syntax. Your example of let proc name = ProcedureBuilder(connStr, factory) is perfectly fine, assuming that ProcedureBuilder is defined appropriately, though you presumably want name to appear somewhere in the constructor arguments.

like image 193
kvb Avatar answered Feb 16 '23 03:02

kvb


The answer from Keith (kvb) is correct - you can use parameterized computation builders. The syntax of computation expressions is:

<expr> { <cexpr> }

So, the builder can be created by any expression. Usually, it is some value (e.g. async) but it can be a function call or even a constructor call. When using this, you would typically define a parameterized builder and then pass the argument to a constructor using a function (as @kvb suggests).

I actually wrote an example of this, not a long time ago, so I can share an example where - I think - this is quite useful. You can find it on F# snippets: http://fssnip.net/4z

The example creates a "special" asynchronous computation builder for ASP.NET MVC that behaves just like standard async. The only difference is that it adds Run member that uses AsyncManager (provided by ASP.NET) to execute the workflow.

Here are some relevant parts from the snippet:

/// A computation builder that is almost the same as stnadard F# 'async'.
/// The differnece is that it takes an ASP.NET MVC 'AsyncManager' as an
/// argumnet and implements 'Run' opration, so that the workflow is 
/// automatically executed after it is created (using the AsyncManager)
type AsyncActionBuilder(asyncMgr:Async.AsyncManager) = 
  // (Omitted: Lots of boilerplate code)

  /// Run the workflow automatically using ASP.NET AsyncManager
  member x.Run(workflow) = 
    // Use 'asyncMgr' to execute the 'workflow'

The snippet wraps the construction in a base class, but you could define a function:

let asyncAction mgr = new AsyncActionBuilder(mgr)

And then use it to define asynchronous action in ASP.NET MVC:

member x.LengthAsync(url:string) = asyncAction x.AsyncManager {
    let wc = new WebClient()
    let! html = wc.AsyncDownloadString(url)
    return html.Length }
like image 36
Tomas Petricek Avatar answered Feb 16 '23 01:02

Tomas Petricek