Skip to content

Instantly share code, notes, and snippets.

@absolutejam
Created August 25, 2022 15:17
Show Gist options
  • Save absolutejam/ce460b0aa6ba0c7d18c713664463aa10 to your computer and use it in GitHub Desktop.
Save absolutejam/ce460b0aa6ba0c7d18c713664463aa10 to your computer and use it in GitHub Desktop.
Brain-dumping a ZIO-inspired DI solution for F# (with some Reader monad flavour)
type Dep<'t> =
abstract member Get : 't
// This doesn't work :'(
// type Deps<'a, 'b>(a: Dep<'a>, b: Dep<'b>) =
// member this.GetA = a
// member this.GetB = b
// interface Dep<'a> with member this.Get with get () = this.GetA
// interface Dep<'b> with member this.Get with get () = this.GetB
module Dep =
let make<'t> t = { new Dep<'t> with member _.Get with get () = t }
let get<'t> (dep: Dep<'t>) = dep.Get
let map<'t> (mapper: 't -> 'u) (dep: Dep<'t>): Dep<'u> = { new Dep<'u> with member _.Get with get () = mapper (get dep) }
let using<'t> (tapper: 't -> unit) (dep: Dep<'t>): unit = tapper (get dep)
// This also doesn't work???
let using2<'dep, 'a, 'b when 'dep :> 'a and 'dep :> 'b> (f: 'a -> 'b -> unit) (dep: Dep<'dep>) =
f (get<'a> dep) (get<'b> dep)
type ILogger =
abstract member Info: message: string -> unit
abstract member Warning: message: string -> unit
abstract member Error: message: string -> unit
module Logger =
let init =
let log level message = printfn $"[{level}] {message}"
{ new ILogger with
member _.Info message = log "INFO" message
member _.Warning message = log "WARNING" message
member _.Error message = log "ERROR" message
}
type DbConnection = DbConnection of string
module DbConnection =
let fromEnv (dep: #Dep<DbConnection>) = dep.Get
//
// Helper modules that leverage Dep-wrapped dependencies
//
module Log =
let fromEnv (dep: #Dep<ILogger>) = dep.Get
let info message (dep: #Dep<ILogger>) =
dep |> Dep.using (fun logger -> logger.Info message)
let warning message (dep: #Dep<ILogger>) =
dep |> Dep.using (fun logger -> logger.Warning message)
let error message (dep: #Dep<ILogger>) =
dep |> Dep.using (fun logger -> logger.Error message)
// Implementation
let loggerDep = Dep.make Logger.init
type MyServiceEnv =
inherit Dep<ILogger>
inherit Dep<DbConnection>
let myServiceEnv =
{ new MyServiceEnv with
member _.Get = loggerDep.Get
member _.Get = DbConnection "x" }
// NB: Type annotations not required
let x : ILogger = Log.fromEnv myServiceEnv
let y : DbConnection = DbConnection.fromEnv myServiceEnv
myServiceEnv
|> Dep.using<ILogger> (fun logger -> logger.Info "A log message")
myServiceEnv
|> Log.info "lol"
// Or, using a computation expression
// Must implement _.Yield (depFun: Dep<'t> -> unit)
dependencies deps {
do! Log.info "Hello!"
do! Dep.using2 (fun logger conn ->
logger.Info "Connecting..."
Db.connect conn
)
let! conn = DbConnection.fromEnv
doThing conn
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment