Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

revskill10 commented Aug 8, 2018

Those let and do is really noisy :(

@isaacabraham

This comment has been minimized.

Copy link
Owner Author

isaacabraham commented Aug 8, 2018

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

This comment has been minimized.

Copy link
Owner Author

isaacabraham commented Aug 8, 2018

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

This comment has been minimized.

Copy link

dpraimeyuu commented Aug 8, 2018

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

This comment has been minimized.

Copy link
Owner Author

isaacabraham commented Aug 8, 2018

@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

This comment has been minimized.

Copy link

giuliohome commented Aug 15, 2018

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

This comment has been minimized.

Copy link

giuliohome commented Aug 17, 2018

@gusty

This comment has been minimized.

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
You can’t perform that action at this time.