Skip to content

Instantly share code, notes, and snippets.

@Porges
Created June 2, 2016 23:41
Show Gist options
  • Save Porges/df9b386a7e5383dbc857ea8fe49f4b95 to your computer and use it in GitHub Desktop.
Save Porges/df9b386a7e5383dbc857ea8fe49f4b95 to your computer and use it in GitHub Desktop.
Using reader monad in F# to pass around dependencies
module Implicit
type Implicitly<'i, 't> = private Implicitly of ('i -> 't)
let implicitly = Implicitly
let runImplicit impl (Implicitly f) = f impl
type ImplicitBuilder<'i> () =
member __.ReturnFrom x = x
member __.Return x = Implicitly (fun _ -> x)
member __.Bind (x : Implicitly<'i, 't>, f : 't -> Implicitly<'i, 'u>) : Implicitly<'i, 'u> =
Implicitly (fun impl -> runImplicit impl (f (runImplicit impl x)))
module MyAPI
open Implicit
type QueryResult = unit // TODO
// dependencies
type InsertStuffDependencies =
{
ReadText : string -> string;
ExecuteQuery : string -> string -> QueryResult
}
// give new names to the existing Implicit types
type InsertStuffAPI<'t> = Implicitly<InsertStuffDependencies, 't>
let api = ImplicitBuilder<InsertStuffDependencies>()
let runApi : InsertStuffDependencies -> _ = runImplicit
// here are the functions you can use:
let readText s : InsertStuffAPI<string> = implicitly (fun impl -> impl.ReadText s)
let executeQuery s1 s2 : InsertStuffAPI<QueryResult> = implicitly (fun impl -> impl.ExecuteQuery s1 s2)
// default implementation of dependencies
let defaults = { ReadText = raise <| System.NotImplementedException(); ExecuteQuery = raise <| System.NotImplementedException() }
module MyProgram
open MyAPI
// here's your insertPerson function
let insertPerson firstName = api {
let! insertSql = readText "C:\InsertPersonQuery.txt"
return! executeQuery insertSql firstName
}
// to run it:
let result = runApi defaults (insertPerson "Hodor")
// but note that now we can decompose the insertPerson function
// and still not have to pass around the dependencies:
let doTheRead = api {
return! readText "C:\InsertPersonQuery.txt"
}
let doTheQuery query firstName = api {
return! executeQuery query firstName
}
let insertPersonDecomposed firstName = api {
let! insertSql = doTheRead
return! doTheQuery insertSql firstName
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment