Skip to content

Instantly share code, notes, and snippets.

@isaacabraham
Last active November 1, 2022 23:58
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save isaacabraham/0849128cc72f26779d3bbd160a97114e to your computer and use it in GitHub Desktop.
Save isaacabraham/0849128cc72f26779d3bbd160a97114e to your computer and use it in GitHub Desktop.
F# port of the first half of John De Goes "FP to the max" (https://www.youtube.com/watch?v=sxudIMiOo68)
#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
@revskill10
Copy link

Those let and do is really noisy :(

@isaacabraham
Copy link
Author

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.

@isaacabraham
Copy link
Author

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.

@dpraimeyuu
Copy link

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 😄

@isaacabraham
Copy link
Author

@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.

@giuliohome
Copy link

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.

@giuliohome
Copy link

giuliohome commented Aug 17, 2018

@gusty
Copy link

gusty commented May 28, 2019

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#+).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment