Created
August 25, 2022 15:17
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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