-
-
Save isaacabraham/0849128cc72f26779d3bbd160a97114e to your computer and use it in GitHub Desktop.
#load @".paket\load\net452\FSharpPlus.fsx" | |
open FSharpPlus | |
open System | |
[<AutoOpen>] | |
module rec IO = | |
let run (IO computation) = computation() | |
type IO<'T> = | |
| IO of (unit -> 'T) | |
static member (>>=) (x, f) = IO(fun () -> run x |> f |> run) | |
static member Return (x:'T) = IO(fun () -> x) | |
[<AutoOpen>] | |
module SideEffects = | |
let private r = Random() | |
let printLn text = IO (fun () -> printfn "%s" text) | |
let readLn() = IO(fun () -> Console.ReadLine()) | |
let random upper = IO(fun () -> r.Next(0, upper)) | |
let parseInt s = Int32.TryParse s |> function | true, x -> Some x | false, _ -> None | |
let rec checkContinue name = monad { | |
do! printLn ("Do you want to continue, " + name + "?") | |
let! answer = readLn() |> map String.toLower | |
return! | |
match answer with | |
| "y" -> IO.Return true | |
| "n" -> IO.Return false | |
| _ -> checkContinue name } | |
let rec gameLoop name = monad { | |
let! secret = random 5 |> map ((+) 1) | |
do! printLn ("Dear " + name + ", please guess a number from 1 to 5:") | |
let! input = readLn() | |
do! | |
match parseInt input with | |
| None -> printLn "You did not enter a number!" | |
| Some x when x = secret -> printLn ("You guessed right, " + name + "!") | |
| Some _ -> printLn (sprintf "You guessed wrong, %s! The number was: %d" name secret) | |
let! shouldContinue = checkContinue name | |
return! | |
if shouldContinue then gameLoop name | |
else IO.Return() } | |
let main = monad { | |
do! printLn "What is your name?" | |
let! name = readLn() | |
do! printLn ("Hello, " + name + " welcome to the game!") | |
do! gameLoop name | |
return() } | |
run main |
Unless you come from a Python background, let
is just instead of e.g. var
/ val
etc. in many other (non-ML) languages - not sure where the real noise there, it's just binding a value to a symbol :-) do!
is how you explicitly execute some unit-returning monadic value - F# doesn't really do "implicit" execution / ignoring of values.
Note that if we didn't have any effects - so removed the whole IO thing - then the do
wouldn't be necessary, but that's the whole point here I thought - to explicitly reason about side-effects.
Really nice implementation! Just wondering about FSharpPlus
- have you ever used it in production? Or maybe do you know anyone that used it? Just being curious if F# can be easily leveraged to something similar as Scala can be turned into using libs like zio/scalaz/cats 😄
@dpraimeyuu thank you :-) but I really just followed Jon's video step by step! I've never used FSharpPlus that much - it was just a time saver here instead of creating a full computation expression - but I know some developers who swear by it. You can get quite far in F# with such libraries which take advantage of SRTPs - kind of a hack in my opinion and there are sharp edges, but they work. Also there's FSharpX as well. But most F# developers and codebases that I've seen don't really use it.
I think that the port of the second part corresponds to translating Scala "capabilities" into F# "effect handlers", as they are defined in this snippet http://www.fssnip.net/7TM/title/TypeSafe-effect-builder
It is the most generic concept of a dsl.
My version here:
https://gist.github.com/giuliohome/7cabc15c38ce22d3532e8046241e0ed7
Here's my simplified version https://gist.github.com/gusty/3a9d654de320225e4a17d92049646160/revisions
Using FSharpPlus tryParse
and the async
monad.
You probably know it already, you can use async
instead of io
(that's the reason why there's no IO monad in F#+).
Those
let
anddo
is really noisy :(