fun-girlz


Wolna monada IO

Co to znaczy, że monada jest wolna? Możemy podzielić monady na dwa typy: takie które wykonują się od razu (np. monada Result) oraz takie, które trzeba wykonać specjalną funkcją po zbudowaniu (np. monada State).

Wolne monady to drugie. Dokładniej rzecz biorąc jest to dużo bardziej skomplikowane, sam ledwo rozumiem zasady działania kategorii wolnych monad. Cel jest taki, że dopóki nie uruchomimy monady, to możemy ją bezkarnie budować i ani kawałek jej kodu nie zostanie wykonany.

Wolne monady mają więc zalety. Między innymi będzie to możliwość powtórzenia wykonania. Np. monadę stanu budujemy raz, a potem możemy wywoływać wiele razy z różnym stanem.

Teraz zbudujemy sobie wolną monadę IO, która w funkcyjny sposób pozwoli nam komunikowac się ze światem.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
type IO<'a> =
    | Pure of 'a
    | PutStrLn of string * (unit -> IO<'a>)
    | GetStrLn of (string -> IO<'a>)
    | ReadFile of name: string * (string -> IO<'a>)
    | WriteFile of name: string * contents: string  * (unit -> IO<'a>)

let rec ioBind (f : 'a -> IO<'b>) (m : IO<'a>) =
    match m with
    | Pure x -> f x
    | PutStrLn (str, next) -> PutStrLn (str, next >> ioBind f)
    | GetStrLn (next) -> GetStrLn (next >> ioBind f)
    | ReadFile (name, next) -> ReadFile (name, next >> ioBind f)
    | WriteFile (name, contents, next) -> WriteFile (name, contents, next >> ioBind f)

type IOBuilder() =
    member this.Return(x) = Pure x
    member this.Bind(m, f) = ioBind f m
    member this.Zero(x) = Pure ()

let io = IOBuilder()

Przygotowaliśmy Monadę zawierającą kilka pożądanych przez nas operacji. W zasadzie dopóki nie napiszemy interpretera dla tej monady, to zbudowanie jej nie będzie miało żadnych efektów.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
let pureNext = fun x -> Pure x
let putStrLn s = PutStrLn (s, pureNext)
let getStrLn = GetStrLn pureNext
let readFile name = ReadFile (name, pureNext)
let writeFile name contents = WriteFile (name, contents, pureNext)

let program =
    io {
        do! putStrLn "Podaj nazwę pliku:"
        let! fname = getStrLn
        let! contents = readFile fname
        do! writeFile fname (contents + "appended line.\n")
    }

Napisaliśmy program, który wczytuje zawartość pliku na podstawie jego nazwy od użytkownika, a następnie dopisuje linijkę. Teraz utworzymy interpreter naszej monady.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let rec interpret program = 
    match program with
    | Pure x -> x
    | PutStrLn (str, next) ->
        do printfn "%s" str
        interpret (next ())
    | GetStrLn next ->
        let s = System.Console.ReadLine()
        interpret (next s)
    | ReadFile (name, next) ->
        let s = System.IO.File.ReadAllText(name)
        interpret (next s)
    | WriteFile (name, cnt, next) ->
        do System.IO.File.WriteAllText(name, cnt)
        interpret (next ())

Kiedy uruchomimy ten interpreter na naszym programie to wtedy po kolei będziemy wykonywać określone czynności.

Pure oznacza po prostu jakąś wartość. PutStrLn niesie ze sobą string do wypisania na ekran oraz będzie zwracać () do funkcji którą przekazuje do bind, a na koniec dostajemy kolejne IO, czyli pewną kontynuację do wykonania. Tworzymy taki łańcuch IO, który krok po kroku wykonujemy. Pozostałem GetStrLn, ReadFile i WriteFile zachowują się bardzo podobnie.

Nie będziemy już mieli zadań, a zamiast tego porozmawiamy sobie o monadzie Async, którą można nieco potraktować jako wolną monadę do asynchronicznych operacji.

type IO<'a> =
  | Pure of 'a
  | PutStrLn of string * (unit -> IO<'a>)
  | GetStrLn of (string -> IO<'a>)
  | ReadFile of name: string * (string -> IO<'a>)
  | WriteFile of name: string * contents: string * (unit -> IO<'a>)

Full name: Io_monad.IO<_>
union case IO.Pure: 'a -> IO<'a>
union case IO.PutStrLn: string * (unit -> IO<'a>) -> IO<'a>
Multiple items
val string : value:'T -> string

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

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

Full name: Microsoft.FSharp.Core.string
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
union case IO.GetStrLn: (string -> IO<'a>) -> IO<'a>
union case IO.ReadFile: name: string * (string -> IO<'a>) -> IO<'a>
union case IO.WriteFile: name: string * contents: string * (unit -> IO<'a>) -> IO<'a>
val ioBind : f:('a -> IO<'b>) -> m:IO<'a> -> IO<'b>

Full name: Io_monad.ioBind
val f : ('a -> IO<'b>)
val m : IO<'a>
val x : 'a
val str : string
val next : (unit -> IO<'a>)
val next : (string -> IO<'a>)
val name : string
val contents : string
Multiple items
type IOBuilder =
  new : unit -> IOBuilder
  member Bind : m:IO<'b> * f:('b -> IO<'c>) -> IO<'c>
  member Return : x:'d -> IO<'d>
  member Zero : x:'a -> IO<unit>

Full name: Io_monad.IOBuilder

--------------------
new : unit -> IOBuilder
val this : IOBuilder
member IOBuilder.Return : x:'d -> IO<'d>

Full name: Io_monad.IOBuilder.Return
val x : 'd
member IOBuilder.Bind : m:IO<'b> * f:('b -> IO<'c>) -> IO<'c>

Full name: Io_monad.IOBuilder.Bind
val m : IO<'b>
val f : ('b -> IO<'c>)
member IOBuilder.Zero : x:'a -> IO<unit>

Full name: Io_monad.IOBuilder.Zero
val io : IOBuilder

Full name: Io_monad.io
val pureNext : x:'a -> IO<'a>

Full name: Io_monad.pureNext
val putStrLn : s:string -> IO<unit>

Full name: Io_monad.putStrLn
val s : string
val getStrLn : IO<string>

Full name: Io_monad.getStrLn
val readFile : name:string -> IO<string>

Full name: Io_monad.readFile
val writeFile : name:string -> contents:string -> IO<unit>

Full name: Io_monad.writeFile
val program : IO<unit>

Full name: Io_monad.program
val fname : string
val interpret : program:IO<'a> -> 'a

Full name: Io_monad.interpret
val program : IO<'a>
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
namespace System
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...

Full name: System.Console
System.Console.ReadLine() : string
namespace System.IO
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
System.IO.File.ReadAllText(path: string) : string
System.IO.File.ReadAllText(path: string, encoding: System.Text.Encoding) : string
val cnt : string
System.IO.File.WriteAllText(path: string, contents: string) : unit
System.IO.File.WriteAllText(path: string, contents: string, encoding: System.Text.Encoding) : unit
F# Project