-
-
Save nojaf/aa8b191e2f21c291fa58e80076f3c92f to your computer and use it in GitHub Desktop.
// Learn more about F# at http://fsharp.org | |
// See the 'F# Tutorial' project for more help. | |
open System | |
let toInt a = | |
match (Int32.TryParse(a)) with | |
| (true, x) -> Some x | |
| (false, _) -> None | |
type MaybeBuilder() = | |
member this.Bind(x, f) = | |
printfn "x: %A, f: %A" x f | |
match x with | |
| None -> None | |
| Some a -> f a | |
member this.Return(x) = | |
Some x | |
let maybe = new MaybeBuilder() | |
[<EntryPoint>] | |
let main argv = | |
printfn "%A" argv | |
let parsed = | |
maybe { | |
let! head = Array.tryHead argv | |
let! v = head |> toInt | |
return v | |
} | |
printfn "parsed is %A" parsed | |
0 // return an integer exit code |
It's because second Bind
is inside f
of first Bind
.
So on the first run your CE expands to something like:
maybe.Bind(None, fun o -> maybe.Bind(...))
With the code as written, the bind will never run the continuation passed as the second argument as it will just return None, making the overall return value of the CE None
As @mavnn mentioned, things might make more sense if you de-sugar the expression.
That f argument that gets passed into Bind is called a continuation, which represents "the next steps after this statement" (I first encountered this pattern when working with async code, it's similar to that). F# for fun and profit has a nice explanation on continuations and Bind which might help clarify things.
In short: every let! is translated to a call to Bind, with the first argument being the option value (x) and the second argument a function that represents all the next steps in the expression (f). Not all those Bind calls get evaluated, so not all those continuations get evaluated either. As soon as you hit a None case in a call to Bind, that Bind call will just return None and not even try to evaluate the continuation, i.e. no additional printfs will be evaluated.
PS: it's not you. This is as tricky as functional programming in F# gets and the official documentation is terrible :)
Thanks for the quick responses!
So if I change the maybe block to use the maybe instance I get:
let parsedTwo =
maybe.Bind
(tryHead argv,
fun head ->
maybe.Bind(toInt head, fun result ->
maybe.Return(result)
)
)
if tryHead argv is None then the fun head -> ...
never get's executed right?
@nojaf correct!
If the code above is ran without a argument the following gets printed:
When given an valid argument:
I'm a bit confused why the first attempt only has one print of line 13?
And if head is
None
the code in the next computation will not be executed but how exactly is None returned?Is it because it is the last value within the maybe block?