-
-
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
letanddois really noisy :(