objects () |> functions


Quatro - Step 3

As with our previous versions, we'll start by adding the RethinkDB driver to project.json; we'll also bring the data-config.json file from Dos/Tres into this project, changing the database name to O2F4. Follow the instructions for Tres up though the point where it says "we'll create a file Data.fs".

Parsing data.config

We'll use Data.fs in this project as well, but we'll do things a bit more functionally. We'll use Chiron to parse the JSON file, and we'll set up a discriminated union (DU) for our configuration parameters.

First, to be able to use Chiron, we'll need the package. Add the following line within the dependencies section:

1: 
"Chiron": "6.2.1",

Then, we'll start Data.fs with our DU.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
namespace Quatro

open Chiron
// other opens
type ConfigParameter =
  | Hostname of string
  | Port     of int
  | AuthKey  of string
  | Timeout  of int
  | Database of string

This DU looks a bit different than the single-case DUs or enum-style DUs that we made in step 2. This is a full-fledged DU with 5 different types, 3 strings and 2 integers. The DataConfig record now becomes dead simple:

1: 
type DataConfig = { Parameters : ConfigParameter list }

We'll populate that using Chiron's Json.parse function.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
with
  static member FromJson json =
    match Json.parse json with
    | Object config ->
        let options =
          config
          |> Map.toList
          |> List.map (fun item ->
              match item with
              | "Hostname", String x -> Hostname x
              | "Port",     Number x -> Port <| int x
              | "AuthKey",  String x -> AuthKey x
              | "Timeout",  Number x -> Timeout <| int x
              | "Database", String x -> Database x
              | key, value ->
                  raise <| InvalidOperationException
                              (sprintf "Unrecognized RethinkDB configuration parameter %s (value %A)" key value))
        { Parameters = options }
    | _ -> { Parameters = [] }

There is a lot to learn in these lines.

  • Before, if the JSON didn't parse, we raised an exception, but that was about it. In this one, if the JSON doesn't parse, we get a default connection. Maybe this is better, maybe not, but it demonstrates that there is a way to handle bad JSON other than an exception.
  • Object, String, and Number are Chiron types (cases of a DU, actually), so our match statement uses the destructuring form to "unwrap" the DU's inner value. For String, x is a string, and for Number, x is a decimal (that's why we run it through int to make our DUs.
  • This version will raise an exception if we attempt to set an option that we do not recognize (something like "databsae" - not that anyone I know would ever type it like that...).

Now, we'll adapt the CreateConnection () function to read this new configuration representation:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
  member this.CreateConnection () : IConnection =
    let folder (builder : Connection.Builder) block =
      match block with
      | Hostname x -> builder.Hostname x
      | Port     x -> builder.Port     x
      | AuthKey  x -> builder.AuthKey  x
      | Timeout  x -> builder.Timeout  x
      | Database x -> builder.Db       x
    let bldr =
      this.Parameters
      |> Seq.fold folder (RethinkDB.R.Connection ())
    upcast bldr.Connect()

Our folder function utilizes a match on our ConfigParameter DU. Each time through, it will return a modified version of the builder parameter, because one of them will match. We then create our builder by folding the parameter, using R.Connection () as our beginning state, then return its Connect () method.

For now, let's copy the rest of Data.fs from Tres to Quatro - this gives us the table constants and the table/index initialization code.

Dependency Injection: Functional Style

One of the concepts that dependency injection is said to implement is "inversion of control;" rather than an object compiling and linking a dependency at compile time, it compiles against an interface, and the concrete implementation is provided at runtime. (This is a bit of an oversimplification, but it's the basic gist.) If you've ever done non-DI/non-IoC work, and learned DI, you've adjusted your thinking from "what do I need" to "what will I need". In the functional world, this is done through a concept called the Reader monad. The basic concept is as follows:

  • We have a set of dependencies that we establish and set up in our code.
  • We a process with a dependency that we want to be injected (in our example, our IConnection is one such dependency).
  • We construct a function that requires this dependency, and returns the result we seek. Though we won't see it in this step, it's easy to imagine a function that requires an IConnection and returns a Post.
  • We create a function that, given our set of dependencies, will extract the one we need for this process.
  • We run our dependencies through the extraction function, to the dependent function, which takes the dependency and returns the result.

Confused yet? Me too - let's look at code instead. Let's create Dependencies.fs and add it to the build order above Entities.fs. This write-up won't expound on every line in this file, but we'll hit the highlights to see how all this comes together. ReaderM is a generic class, where the first type is the dependency we need, and the second type is the type of our result.

After that (which will come back to in a bit), we'll create our dependencies, and a function to extract an IConnection from it.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
type IDependencies =
  abstract Conn : IConnection

[<AutoOpen>]
module DependencyExtraction =

  let getConn (deps : IDependencies) = deps.Conn

Our IDependencies are pretty lean right now, but that's OK; we'll flesh it out in future steps. We also wrote a dead-easy function to get the connection; the signature is literally IDependencies -> IConnection. No ReaderM funkiness yet!

Now that we have a dependency "set" (of one), we need to go to App.fs and make sure we actually have a concrete instance of this for runtime. Add this just below the module declaration:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
  let lazyCfg = lazy (File.ReadAllText "data-config.json" |> DataConfig.FromJson)
  let cfg = lazyCfg.Force()
  let deps = {
    new IDependencies with
      member __.Conn
        with get () =
          let conn = lazy (cfg.CreateConnection ())
          conn.Force()
  }

Here, we're using lazy to do this once-only-and-only-on-demand, then we turn around and pretty much demand it. If you're thinking this sounds a lot like singletons - your thinking is superb! That's exactly what we're doing here. We're also using F#'s inline interface declaration to create an implementation without creating a concrete class in which it is held.

Maybe being our own IoC container isn't so bad! Now, let's take a stab at actually connection, and running the EstablishEnvironment function on startup. At the top of main:

1: 
2: 
3: 
    let initDb (conn : IConnection) = conn.EstablishEnvironment cfg.Database |> Async.RunSynchronously 
    let start = liftDep getConn initDb
    start |> run deps

But wait - we don't have a Database property on our data config; our configuration is just a list of ConfigParameter selections. No worries, though; we can expose a database property on it pretty easily.

1: 
2: 
3: 
4: 
5: 
6: 
  member this.Database =
    match this.Parameters
          |> List.filter (fun x -> match x with Database _ -> true | _ -> false)
          |> List.tryHead with
    | Some (Database x) -> x
    | _ -> RethinkDBConstants.DefaultDbName

OK - now our red squiggly lines are gone. Now, if Jiminy Cricket had written F#, he would have told Pinocchio "Let the types be your guide". So, how are we doing with these? initDb has the signature IConnection -> unit, start has the signature ReaderM<IDependencies, unit>, and the third line is simply unit. And, were we to run it, it would work, but... it's not really composable.

Creating extension methods on objects works great in C#-land, and as we've seen, it works the same way in F#-land. However, in the case where we want to write functions that expect an IConnection and return our expected result, extension methods are not what we need. Let's change our AutoOpened DataExtensions module to something like this:

1: 
2: 
3: 
4: 
5: 
[<RequireQualifiedAccess>]
module Data =
  let establishEnvironment database conn =
    let r = RethinkDB.R
    // etc.

Now, we have a function with the signature string -> IConnection -> Async<unit>. This gets us close, but we still have issues on either side of that signature. On the front, if we were just hard-coding the database name, we could drop the string parameter, and we'd have our IConnection as the first parameter. On the return value, we will need to run the Async workflow (remember, in F#, they're not started automatically); we need unit, not Async<unit>.

We'll use two key F# concepts to fix this up. Currying (also known as partial application) allows us to look at every return value that isn't the result as a function that's one step closer. Looking at our signature above, you could express it in English as "a function that takes a string, and returns a function that takes an IConnection and returns an Async workflow." So, to get a function that starts with an IConnection, we just provide the database name.

1: 
    let almost = Data.establishEnvironment cfg.Database

The signature for almost is IConnection -> Async<unit>. Just what we want. For the latter, we use composition. This is a word that can be used, for example, to describe the way the collection modules expect the collection as the final parameter, allowing the output of one to be piped, using the |> operator, to the input of the next. The other is with the >> operator, which says to use the output of the first function as the input of the second function, creating a single function from the two. This is the one we'll use to run our Async workflow.

1: 
    let money = Data.establishEnvironment cfg.Database >> Async.RunSynchronously

The signature for money is now IConnection -> unit, just like we need.

Now, let's revisit initDb above. Since we don't need the IConnection as a parameter, we can change that definition to the same thing we have for money above. And, since we don't need the parameter, we can just inline the call after getConn; we'll just need to wrap the expression in parentheses to indicate that it's a function on its own. And, we don't need the definition of start anymore either - we can just pipe our entire expression into run deps.

1: 
2: 
    liftDep getConn (Data.establishEnvironment cfg.Database >> Async.RunSynchronously)
    |> run deps

It works! We set up our dependencies, we composed a function using a dependency, and we used a Reader monad to make it all work. But, how did it work? Given what we just learned above, let's look at the steps; we're coders, not magicians.

First up, liftDeps.

1: 
  let liftDep (proj : 'd2 -> 'd1) (rm : ReaderM<'d1, 'output>) : ReaderM<'d2, 'output> = proj >> rm

The proj parameter is defined as a function that takes one value and returns another one. The rm parameter is a Reader monad that takes the return value of proj, and returns a Reader monad that takes the parameter value of proj and returns an output type. We passed getConn as the proj parameter, and its signature is IDependencies -> IConnection; the second parameter was a function with the signature IConnection -> unit. Where does this turn into a ReaderM? Why, the definition, of course!

1: 
type ReaderM<'d, 'out> = 'd -> 'out

So, liftDep derived the expected ReaderM type from getConn; 'd1 is IDependencies and 'd2 is IConnection. This means that the next parameter should be a function which takes an IConnection and returns the output of the type we expect. Since we pass in IConnection -> unit, output is unit. When all is said and done, if we were to assign a value to the top line, we would end up with ReaderM<IDependencies, unit>.

Now, to run it. run is defined as:

1: 
  let run dep (rm : ReaderM<_,_>) = rm dep

This is way easier than what we've seen up to this point. It takes an object and a ReaderM, and applies the object to the first parameter of the monad. By |>ing the ReaderM<IDependencies, unit> to it, and providing our IDependencies instance, we receive the result; the reader has successfully encapsulated all the functions below it. From this point on, we'll just make sure our types are correct, and we'll be able to utilize not only an IConnection for data manipulation, but any other dependencies we may need to define.

Take a deep breath. Step 3 is done, and not only does it work, we understand why it works.

Back to Step 3

module Chiron
type ConfigParameter =
  | Hostname of string
  | Port of int
  | AuthKey of string
  | Timeout of int
  | Database of string

Full name: Quatro.ConfigParameter
union case ConfigParameter.Hostname: string -> ConfigParameter
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
union case ConfigParameter.Port: int -> ConfigParameter
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
union case ConfigParameter.AuthKey: string -> ConfigParameter
union case ConfigParameter.Timeout: int -> ConfigParameter
union case ConfigParameter.Database: string -> ConfigParameter
namespace RethinkDb
namespace RethinkDb.Driver
namespace RethinkDb.Driver.Net
namespace System
namespace System.IO
type ReaderM<'d,'out> = 'd -> 'out

Full name: Quatro.ReaderM<_,_>
val run : dep:'a -> rm:ReaderM<'a,'b> -> 'b

Full name: Quatro.Reader.run
val dep : 'a
val rm : ReaderM<'a,'b>
val liftDep : proj:('d2 -> 'd1) -> rm:ReaderM<'d1,'output> -> ReaderM<'d2,'output>

Full name: Quatro.Reader.liftDep
val proj : ('d2 -> 'd1)
val rm : ReaderM<'d1,'output>
module Reader

from Quatro
type DataConfig =
  {Parameters: ConfigParameter list;}
  member CreateConnection : unit -> IConnection
  member Database : string
  static member FromJson : json:'a -> DataConfig

Full name: Quatro.DataConfig
DataConfig.Parameters: ConfigParameter list
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
static member DataConfig.FromJson : json:'a -> DataConfig

Full name: Quatro.DataConfig.FromJson
val json : 'a
Multiple items
module Json

from Chiron.Mapping

--------------------
module Json

from Chiron.Formatting

--------------------
module Json

from Chiron.Parsing

--------------------
module Json

from Chiron.Optics

--------------------
module Json

from Chiron.Functional

--------------------

--------------------
type Json<'a> = Json -> JsonResult<'a> * Json

Full name: Chiron.Functional.Json<_>
Multiple items
type Object =
  new : unit -> obj
  member Equals : obj:obj -> bool
  member GetHashCode : unit -> int
  member GetType : unit -> Type
  member ToString : unit -> string
  static member Equals : objA:obj * objB:obj -> bool
  static member ReferenceEquals : objA:obj * objB:obj -> bool

Full name: System.Object

--------------------
Object() : unit
val config : Map<string,Json>
val options : ConfigParameter list
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val toList : table:Map<'Key,'T> -> ('Key * 'T) list (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.toList
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val item : string * Json
Multiple items
type String =
  new : value:char -> string + 7 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 2 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int
  ...

Full name: System.String

--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
val x : string
union case Json.Number: decimal -> Json
val x : decimal
val key : string
val value : Json
val raise : exn:Exception -> 'T

Full name: Microsoft.FSharp.Core.Operators.raise
Multiple items
type InvalidOperationException =
  inherit SystemException
  new : unit -> InvalidOperationException + 2 overloads

Full name: System.InvalidOperationException

--------------------
InvalidOperationException() : unit
InvalidOperationException(message: string) : unit
InvalidOperationException(message: string, innerException: exn) : unit
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val this : DataConfig
member DataConfig.Database : string

Full name: Quatro.DataConfig.Database
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val x : ConfigParameter
val tryHead : list:'T list -> 'T option

Full name: Microsoft.FSharp.Collections.List.tryHead
union case Option.Some: Value: 'T -> Option<'T>
Multiple items
type RethinkDBConstants =
  new : unit -> RethinkDBConstants
  static val DefaultDbName : string
  static val DefaultHostname : string
  static val DefaultAuthkey : string
  static val DefaultPort : int
  static val DefaultTimeout : int
  nested type Protocol

Full name: RethinkDb.Driver.RethinkDBConstants

--------------------
RethinkDBConstants() : unit
field RethinkDBConstants.DefaultDbName = "test"
member DataConfig.CreateConnection : unit -> IConnection

Full name: Quatro.DataConfig.CreateConnection
type IConnection =
  member RunAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<obj>
  member RunAtomAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<'T>
  member RunCursorAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<Cursor<'T>>
  member RunNoReply : term:ReqlAst * globalOpts:obj -> unit
  member RunResultAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<'T>

Full name: RethinkDb.Driver.Net.IConnection
val folder : (Connection.Builder -> ConfigParameter -> Connection.Builder)
val builder : Connection.Builder
type Connection =
  member CheckOpen : unit -> unit
  member ClientEndPoint : IPEndPoint
  member Close : ?shouldNoReplyWait:bool -> unit
  member Db : string
  member Dispose : unit -> unit
  member HasError : bool
  member Hostname : string
  member NoReplyWait : unit -> unit
  member NoReplyWaitAsync : ?cancelToken:CancellationToken -> Task
  member Open : bool
  ...
  nested type Builder

Full name: RethinkDb.Driver.Net.Connection
type Builder =
  new : unit -> Builder
  member AuthKey : key:string -> Builder
  member Connect : unit -> Connection
  member ConnectAsync : unit -> Task<Connection>
  member Db : val:string -> Builder
  member Hostname : val:string -> Builder
  member Port : val:int -> Builder
  member Timeout : val:int -> Builder
  member User : user:string * password:string -> Builder

Full name: RethinkDb.Driver.Net.Connection.Builder
val block : ConfigParameter
Connection.Builder.Hostname(val: string) : Connection.Builder
val x : int
Connection.Builder.Port(val: int) : Connection.Builder
Connection.Builder.AuthKey(key: string) : Connection.Builder
Connection.Builder.Timeout(val: int) : Connection.Builder
Connection.Builder.Db(val: string) : Connection.Builder
val bldr : Connection.Builder
module Seq

from Microsoft.FSharp.Collections
val fold : folder:('State -> 'T -> 'State) -> state:'State -> source:seq<'T> -> 'State

Full name: Microsoft.FSharp.Collections.Seq.fold
Multiple items
type RethinkDB =
  inherit TopLevel
  new : unit -> RethinkDB
  member Connection : unit -> Builder
  member ConnectionPool : unit -> Builder
  static val R : RethinkDB

Full name: RethinkDb.Driver.RethinkDB

--------------------
RethinkDB() : unit
field RethinkDB.R
RethinkDB.Connection() : Connection.Builder
Connection.Builder.Connect() : Connection
type IDependencies =
  interface
    abstract member Conn : IConnection
  end

Full name: Quatro.IDependencies
abstract member IDependencies.Conn : IConnection

Full name: Quatro.IDependencies.Conn
Multiple items
type AutoOpenAttribute =
  inherit Attribute
  new : unit -> AutoOpenAttribute
  new : path:string -> AutoOpenAttribute
  member Path : string

Full name: Microsoft.FSharp.Core.AutoOpenAttribute

--------------------
new : unit -> AutoOpenAttribute
new : path:string -> AutoOpenAttribute
val getConn : deps:IDependencies -> IConnection

Full name: Quatro.DependencyExtraction.getConn
val deps : IDependencies
property IDependencies.Conn: IConnection
namespace System.Threading
namespace System.Threading.Tasks
type AsyncBuilder =
  private new : unit -> AsyncBuilder
  member Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
  member Combine : computation1:Async<unit> * computation2:Async<'T> -> Async<'T>
  member Delay : generator:(unit -> Async<'T>) -> Async<'T>
  member For : sequence:seq<'T> * body:('T -> Async<unit>) -> Async<unit>
  member Return : value:'T -> Async<'T>
  member ReturnFrom : computation:Async<'T> -> Async<'T>
  member TryFinally : computation:Async<'T> * compensation:(unit -> unit) -> Async<'T>
  member TryWith : computation:Async<'T> * catchHandler:(exn -> Async<'T>) -> Async<'T>
  member Using : resource:'T * binder:('T -> Async<'U>) -> Async<'U> (requires 'T :> IDisposable)
  ...

Full name: Microsoft.FSharp.Control.AsyncBuilder
val x : AsyncBuilder
member AsyncBuilder.Bind : t:Task<'T> * f:('T -> Async<'R>) -> Async<'R>

Full name: Quatro.ExampleExtensions.Bind


 An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
 a standard .NET task
val t : Task<'T>
Multiple items
type Task =
  new : action:Action -> Task + 7 overloads
  member AsyncState : obj
  member ContinueWith : continuationAction:Action<Task> -> Task + 9 overloads
  member CreationOptions : TaskCreationOptions
  member Dispose : unit -> unit
  member Exception : AggregateException
  member Id : int
  member IsCanceled : bool
  member IsCompleted : bool
  member IsFaulted : bool
  ...

Full name: System.Threading.Tasks.Task

--------------------
type Task<'TResult> =
  inherit Task
  new : function:Func<'TResult> -> Task<'TResult> + 7 overloads
  member ContinueWith : continuationAction:Action<Task<'TResult>> -> Task + 9 overloads
  member Result : 'TResult with get, set
  static member Factory : TaskFactory<'TResult>

Full name: System.Threading.Tasks.Task<_>

--------------------
Task(action: Action) : unit
Task(action: Action, cancellationToken: Threading.CancellationToken) : unit
Task(action: Action, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj) : unit
Task(action: Action, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken) : unit
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit

--------------------
Task(function: Func<'TResult>) : unit
Task(function: Func<'TResult>, cancellationToken: Threading.CancellationToken) : unit
Task(function: Func<'TResult>, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj) : unit
Task(function: Func<'TResult>, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken) : unit
Task(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
val f : ('T -> Async<'R>)
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
member AsyncBuilder.Bind : t:Task * f:(unit -> Async<'R>) -> Async<'R>


 An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
 a standard .NET task which does not commpute a value

member AsyncBuilder.Bind : t:Task<'T> * f:('T -> Async<'R>) -> Async<'R>


 An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
 a standard .NET task

member AsyncBuilder.Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
static member Async.AwaitTask : task:Task -> Async<unit>
static member Async.AwaitTask : task:Task<'T> -> Async<'T>
member AsyncBuilder.Bind : t:Task * f:(unit -> Async<'R>) -> Async<'R>

Full name: Quatro.ExampleExtensions.Bind


 An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
 a standard .NET task which does not commpute a value
val t : Task
val f : (unit -> Async<'R>)
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
member AsyncBuilder.ReturnFrom : t:Task<'T> -> Async<'T>

Full name: Quatro.ExampleExtensions.ReturnFrom
Multiple items
type RequireQualifiedAccessAttribute =
  inherit Attribute
  new : unit -> RequireQualifiedAccessAttribute

Full name: Microsoft.FSharp.Core.RequireQualifiedAccessAttribute

--------------------
new : unit -> RequireQualifiedAccessAttribute
Multiple items
namespace System.Data

--------------------
namespace Microsoft.FSharp.Data
val establishEnvironment : database:string -> conn:IConnection -> Async<unit>

Full name: Quatro.Data.establishEnvironment
val database : string
val conn : IConnection
val r : RethinkDB
val checkDatabase : (string -> Async<unit>)
val db : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val checkTables : (unit -> Async<unit>)
val checkIndexes : (unit -> Async<unit>)
val indexesFor : ('a -> Async<string list>)
val tbl : 'a
Ast.TopLevel.Table(expr: obj) : Ast.Table
val this : IConnection
member IConnection.EstablishEnvironment : database:string -> Async<unit>

Full name: Quatro.IConnectionExtensions.EstablishEnvironment
module App

from Quatro
val lazyCfg : Lazy<DataConfig>

Full name: Quatro.App.lazyCfg
type File =
  static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendText : path:string -> StreamWriter
  static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
  static member Create : path:string -> FileStream + 3 overloads
  static member CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  static member Encrypt : path:string -> unit
  static member Exists : path:string -> bool
  ...

Full name: System.IO.File
File.ReadAllText(path: string) : string
File.ReadAllText(path: string, encoding: Text.Encoding) : string
static member DataConfig.FromJson : json:'a -> DataConfig
val cfg : DataConfig

Full name: Quatro.App.cfg
member Lazy.Force : unit -> 'T
val deps : IDependencies

Full name: Quatro.App.deps
val conn : Lazy<IConnection>
member DataConfig.CreateConnection : unit -> IConnection
val main : 'a -> int

Full name: Quatro.App.main
val initDb : (IConnection -> unit)
member IConnection.EstablishEnvironment : database:string -> Async<unit>
property DataConfig.Database: string
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T
val start : ReaderM<IDependencies,unit>
Multiple items
module Data

from Quatro

--------------------
namespace System.Data

--------------------
namespace Microsoft.FSharp.Data
val almost : (IConnection -> Async<unit>)
val money : (IConnection -> unit)
Fork me on GitHub