In the last
post,
I built out a basic parser monad in C#. While the approach worked OK,
the syntax is still a little foreign to your typical .NET programmer,
what with it’s nested anonymous functions and all. Now, I’m going to
translate that code to F# and take a look at the special monadic syntax
F# supports that makes using monads as easy any sequential code.
First, let’s translate our Parser delegate, Bind, Result and Item
functions over to F#. Just for kicks, let’s also port over the final
version of TwoItems too.
type Parser<'input, 'result> = 'input-> ('result * 'input) option
// the Bind function, defined as a custom operator
let (>>=) p f : Parser<'i,'r> =
fun input ->
match p input with
| Some(value, input) -> (f value) input
| None -> None
let Result v : Parser<'i,'r> = fun input -> Some(v, input)
let Item : Parser<string, char> =
fun input ->
if string.IsNullOrEmpty(input)
then None
else Some(input.[0], input.Substring(1))
let BestTwoItems =
Item >>= (fun v1 ->
Item >>= (fun v2 ->
Result (sprintf "%c%c" v1 v2)))
First, we start with the declaration of the Parser type. Unlike C#, F#
has built in support for tuples, so I didn’t bother to define a Result
type (just the Result function). A Parser is declared to be a function
that takes in some generic input type and returns an optional tuple
pairing the result with the remaining input to be parsed. As I’ve
blogged before, F#’s option type is kinda like C#’s Nullable type, so
a parser that returns None is considered to have failed to parse the
input.
Next up is are the monad functions Bind and Result. The only significant
change from the C# version is that I used the custom operator >>= for
the Bind function. So instead of calling Item().Bind(some\_function)
,
we can call Item \>\>= some\_function
. F# functions aren’t attached
to a type like C# extension methods are, so this is the only way to get
the more readable infix notation. I’m using >>= as the bind operator
because that’s the operator Haskell
uses for their monad
function. Other than the custom operator name, Bind and Result work
identically to their C# counterparts. Note, I explicitly specified the
return type of Bind, Result and Item, but I didn’t have to. F# can
infer the types of all the parameters from usage just fine. I added the
type specifications for the reader, in case you’re not familiar with
F#’s syntax.
Likewise, Item is identical to the C# version including using strings
as the parse input, except for than the F# syntax. Typically, in a real
parsing app you would use an intrinsic list of chars instead of strings,
since F#s list is a much more efficient data structure than strings for
operations that strip characters off the head of the list (like parsers
are wont to do). However, I wanted to make this code as similar to the
previous code, so I stuck with strings.
Finally, we have BestTwoItems. Again, syntax aside, it’s exactly like
it’s C# cousin though I did use the slightly more compact sprintf
function instead of string.Format. Again, while BestTwoItems it works
well, it uses the same nested anonymous function syntax from the C#
version. Maybe I shouldn’t have called it “BestTwoItems”!
However, in F# it’s possible to define a custom syntax for your monad
that let’s you write the function this way:
let VeryBestTwoItems =
parse {
let! v1 = Item
let! v2 = Item
return sprintf "%c%c" v1 v2 }
With this monadic syntax, we’ve now completely eliminated not only the
Parser delegate and the input string, but also the nested anonymous
functions needed by the Bind function, making the code appear completely
sequential.
The secret to making this work is the parse monad object. It the code
above, the word parse almost feels like a language keyword, but it’s
not. It’s actually an instance of an parse monad object with a specific
signature. F# knows how to take the syntax above and combine it with
the parse monad object to produce the right code. Here’s the parse
monad:
type ParseMonad() =
member w.Delay(f) = fun input -> f () input
member w.Return(v) = Result v
member w.Bind(p, f) = p >>= f
let parse = ParseMonad()
As you can see, there’s an obvious direct correlation of Result and Bind
functions we defined last time and the Return and Bind methods in the
ParseMonad. The only thing we haven’t seen before is the Delay method.
Monads are one of of F#’s many delayed
expressions.
F# wraps the entire monad in a call to Delay to ensure the monad isn’t
executed prematurely.
As per the F# grammar
spec,
there are several other functions you can define on your monad if you so
choose. My “real” parser monad also implements Zero and Combine. Zero
returns a parser that unconditionally fails. By defining Zero on my
monad object, I can write ifs without elses, the parser monad will
implicitly inject Zero clause as your else statement. Combine combines
results (I know, a shocker!). I use it as a prioritized choice. In other
words, when you Combine two parsers, you only call the second parser if
calling the first parsers fails. Prioritized Choice is used very often
in PEGs, which is why I chose to define it this way.
F# monadic syntax also support For, Let, While, Using, TryFinally and
TryWith. Frankly, I haven’t spent much time thinking about scenarios
where you’d use these other syntax elements. The only one that’s obvious
to me is Using for deterministic finalization, which you could see using
anywhere you access IDispoasble objects. Here’s hoping the F# folks
document in detail how to use this powerful syntax.
So that’s it for basic monads in F#. I’ve gotten some great comments
(and one less than great comments) as I’ve written this series. In my
last post on monads (in this series at least) I’ll repost some of those
comments as well as provide some concluding thoughts.