Skip to content

Instantly share code, notes, and snippets.

@dadhi
Created March 11, 2019 12:51
Show Gist options
  • Save dadhi/27c44a264a61dfa1d8fce692bccac32e to your computer and use it in GitHub Desktop.
Save dadhi/27c44a264a61dfa1d8fce692bccac32e to your computer and use it in GitHub Desktop.
An F# toy example of Scala ZIO Environment
module EnvDemo
open System
open System.IO
[<Struct>]
type Nothing =
private
| Nothing
[<NoEquality;NoComparison>]
type Env<'r,'e,'a> =
| Env of ('r -> Result<'a,'e>)
module Env =
let run s (Env run) = run s
let unit a = Env (fun _ -> Ok a)
let get<'s,'e> : Env<'s,'e,'s> = Env Ok
let map (f:'a->'b) (env:Env<'r,'e,'a>) =
Env (fun s ->
match run s env with
| Ok a -> f a |> Ok
| Error e -> Error e
)
let bind (f:'a->Env<'r,'e,'b>) (env:Env<'r,'e,'a>) =
Env (fun s ->
match run s env with
| Ok a -> run s (f a)
| Error e -> Error e
)
let bindFromAny (f:'a->Env<'r,'e,'b>) (env:Env<'r,Nothing,'a>) =
Env (fun s ->
match run s env with
| Ok a -> run s (f a)
| _ -> Unchecked.defaultof<_>
)
let bindToAny (f:'a->Env<'r,Nothing,'b>) (env:Env<'r,'e,'a>) =
Env (fun s ->
match run s env with
| Ok a ->
match run s (f a) with
| Ok b -> Ok b
| _ -> Unchecked.defaultof<_>
| Error e -> Error e
)
let map2 s1 s2 f =
bind (fun a -> map (fun b -> f a b) s2) s1
type EnvBuilder<'e>() =
member __.Bind(env:Env<'r,'e,'a>, f:'a -> Env<'r,Nothing,'b>) =
bindToAny f env
member __.Bind(env:Env<'r,Nothing,'a>, f:'a -> Env<'r,'e,'b>) =
bindFromAny f env
member __.Bind(env:Env<'r,'e,'a>, f:'a -> Env<'r,'e,'b>) =
bind f env
member __.Return(value) = unit value
member __.ReturnFrom(value) = value
member __.Yield(value) = unit value
member __.Zero() = unit()
member __.Combine(s1:Env<'s,'e,unit>, s2:Env<'s,'e,'a>) = map2 s1 s2 (fun _ s -> s)
member __.Delay(f) = f()
member __.For(xs:seq<'a>, f:'a -> Env<'s,'e,'a>) = xs |> Seq.map f
member __.Run(value) = value
[<AutoOpen>]
module EnvAutoOpen =
let env<'e> = Env.EnvBuilder<'e>()
type ConsoleService =
abstract member WriteLine : string -> Env<'r,Nothing,unit>
abstract member ReadLine : unit -> Env<'r,IOException,string>
type Console =
abstract member Console : ConsoleService
module Console =
let writeLine s = Env.bind (fun (c:#Console) -> c.Console.WriteLine s) Env.get
let readLine() = Env.bind (fun (c:#Console) -> c.Console.ReadLine()) Env.get
type LoggingService =
abstract member Log : string -> Env<'r,Nothing,unit>
type Logger =
abstract member Logging : LoggingService
module Logger =
let log s = Env.bind (fun (l:#Logger) -> l.Logging.Log s) Env.get
type PersistenceService =
abstract member Persist : 'a -> Env<'r,IOException,unit>
type Persistence =
abstract member Persistence : PersistenceService
module Persistence =
let persist a = Env.bind (fun (p:#Persistence) -> p.Persistence.Persist a) Env.get
let program() =
env<IOException> {
do! Logger.log "started"
do! Console.writeLine "Please enter your name:"
let! name = Console.readLine()
do! Logger.log ("got name = " + name)
do! Persistence.persist name
do! Console.writeLine ("Hi "+name)
do! Logger.log "finished"
return 0
}
let testConsole =
{ new ConsoleService with
member __.WriteLine s =
Env.unit ()
member __.ReadLine() =
Env.unit "hi"
}
let testLogging =
{ new LoggingService with
member __.Log s =
Env.unit ()
}
let testPersistence =
{ new PersistenceService with
member __.Persist a =
Env.unit ()
}
type TestEnv() =
interface Console with member __.Console = testConsole
interface Logger with member __.Logging = testLogging
interface Persistence with member __.Persistence = testPersistence
let test() =
Env.run (TestEnv()) (program())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment