F# generic workflow delegate from task sequence

Recently Gauthier Segay @gauthier from fsharp slack #code channel suggested a separation of concern between connection and command, still preserving their use statement safe auto disposal.

 

181-512

The missing part that I’m describing here is how to make it works under async I/O, the low level P/Invoke I/O completion port mechanism (more about the Unix version here).

The first winning trick it to generalize the context with from an object obj to a category <’a>, so to speak.

let doWithConnection<'a> (dbName : string) (toDo: (unit -> SQLiteCommand) -> Task<'a>) = task {
    let connStr = sprintf "Data Source=%s;Version=3;" dbName
    use conn = new SQLiteConnection(connStr)
    Debug.WriteLine("opening a new connecton!")
    let createCommand = fun () -> conn.CreateCommand()
    do! conn.OpenAsync()
    let! res = toDo createCommand
    return res
}

Now we go on with the TaskBuilder computation expression and apply it also to the async version of the command as well.

let readImportDate (createCommand: unit -> SQLiteCommand) = task {
    use cmd = createCommand ()
    cmd.CommandText <-  @"select max(XlsxTag) from xlsx_imports "
    return! cmd.ExecuteScalarAsync() 
}

Finally, we achieve the goal by double wrapping multiple tasks of commands under a unique connection workfow! It’s a sort of monadic (sequential, one connection) instead of applicative (parallel, more connections) workflow.

let readMultiCmd dbName = task {
  
    return! doWithConnection dbName (fun createCommand ->
 
                task {
                    let! importDate = readImportDate createCommand
                    let! negotiationFrom = readNegotiationFrom createCommand
                    let! negotiationTo = readNegotiationTo createCommand
                    let! cpdDateFrom = readCPDateFrom createCommand
                    let! cpdDateTo = readCPDateTo createCommand
                    let! activeContr = readActiveContr createCommand
                    let! deletedContr = readDeletedContr createCommand
                    return (importDate, 
                             negotiationFrom, negotiationTo,
                             cpdDateFrom , cpdDateTo, 
                             activeContr, deletedContr)
                })
 }