I recently found a nice example of applicative functor validation in Scala (using Scalaz) by Chris Marshall, and decided to port it to F# and C# using FSharpx.
I blogged about applicative functor validation before, in F# and in C#.
When trying to port the Scala code to F# I found there were a few missing general functions in FSharpx, notably sequence
and mapM
. These are one- or two-liners, I ported them from Haskell, as it's syntactically closer to F# than Scala. Hoogle is always a big help for this.
Here is the original code in Scala; here's the F# port and here's the C# port.
I'm not going to copy it here: it's 160 lines of F# and 250 lines of C#.
This example also makes for a nice comparison of these three languages (or four, if you count the implicit presence of Haskell). There are a few little differences in the ports, it's not a literal translation, but you can still see how Scala, being semantically closer to Haskell than either F# or C#, achieves more generality. As for type inference, the F# version requires almost no type annotations, while C# needs the most type annotations, and Scala is somewhere in the middle. This actually depends on what you consider a type annotation.
I chose to make Person immutable in C# to reflect more accurately the equivalent F# and Scala code, but it's not really instrumental to this example. Still, it shows how verbose it is to create a truly immutable class in C#. The C# dev team at Microsoft seems to highly value immutability, so I still have hopes that a future version of C# will improve this situation.
The ability to define custom operators in Scala and F#, like <!>
or *>
(an ability that C# lacks) also makes it easier to work with different ways of composing functions. FSharpx also offers 'named' versions for many of these operators, for example <!>
is simply 'map' and <*>
is 'ap'. Despite what some people say, I think custom operators enable better readability once you know the concepts behind them. Remember that at some point you also learned what '=', '%' and '+' mean.
In particular, the F# port shows the Kleisli composition operator >=>
which I haven't seen mentioned in F# before. This operator is like the regular function composition operator >>
except it works for monadic functions a -> m b
. Compare the signatures for >>
and >=>
for Option:
(>>) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c (>=>) : ('a -> 'b option) -> ('b -> 'c option) -> 'a -> 'c option
I'm quite pleased with the results of this port, even if I do say so myself. This example shows again that many higher concepts in functional programming commonly applied in Haskell are applicable, useful and usable in F# and even in C#. The lack of typeclasses and type constructor abstraction in .NET means some code duplication (mapM for example has to be defined for each monad), but this duplication is on the side of library code in many cases, and so client code isn't that badly affected.
Homework: port this example to Gustavo's fork of FSharpx.
I've sporadically studied F# and functional programming for the last couple years. I never really understood Monads until I read this essay http://cdsmith.wordpress.com/2012/04/18/why-do-monads-matter. Now Your article makes perfect sense to me.
ReplyDeleteDo you remember your personal process of comprehending Monads? It seems most people who do not understand have a hard time grasping it. And people who do understand cannot effectively teach it.
It's also possible my problem has been not finding the time for serious devoted study.
I think custom operators are great too, but now that I've discovered the power of using the full range of unicode characters in F# it would be great to create an operator like ∛. I've never studied the compiler to understand the limitation of operators to certain symbols. Maybe in some future version you could wrap unicode characters within operator legal symbols, like >∛>.
ReplyDeleteThank for the great example code for this post.
ReplyDeleteI've ported the Scala code to Ruby here: https://gist.github.com/2553490
What do you think of the use of applicative validation in a dynamic language like Ruby?
I've been studying F# for a couple months now, and though the first month or so was a little rough, I quickly fell in love with the language.
ReplyDeleteThe pipe operators and composition are awesome in and of themselves, but the fact that you can build on them is just amazing.
ex:
let inline zipper2 ( fn : _->_ ) = ( fun (a1, a2) -> fn a1 a2 ) : _->_
let inline zipper3 ( fn : _->_ ) = ( fun (a1, a2, a3) -> fn a1 a2 a3 ) : _->_
let inline zipper4 ( fn : _->_ ) = ( fun (a1, a2, a3, a4) -> fn a1 a2 a3 a4 ) : _->_
let inline zipper5 ( fn : _->_ ) = ( fun (a1, a2, a3, a4, a5) -> fn a1 a2 a3 a4 a5 ) : _->_
let inline funnel (zipper) (arged) (next) = (zipper arged)>>next
let inline (|>>) fn1 fn2 = funnel zipper2 fn1 fn2
let inline (||>>) fn1 fn2 = funnel zipper3 fn1 fn2
let inline (|||>>) fn1 fn2 = funnel zipper4 fn1 fn2
let inline (||||>>) fn1 fn2 = funnel zipper5 fn1 fn2
let build (x:int) (s:string) (e:string) = (String.replicate x s) + e
let build2 (s:string) = s |> printfn "%A"
let testfun = build ||>> build2
What may look to be tedious at first glance (i.e. someFun |> fun one two -> otherFun(one,two)) can be rendered permanently trivial with less than a page of code.
F# is a truly amazing language.
Wow, I totally missed the comments on this post. I'm sorry for the late replies.
ReplyDelete@jackfoxy : I remember struggling with Monads myself, but as it usually happens, once you grok them it's hard to remember what it was like not understanding them. IIRC one of the turning points was implementing a monad so I could actually see the composition happening. In my case at least, syntax sugar like do notation in Haskell and computation expressions in F# actually made it harder to understand monads. Desugared monads makes it easier to see what's going on. Once you understand things, you can choose whether to use sugar or not depending on what you're writing.
About unicode operators: Haskell has had this ability for quite some time now, but it seems not many people use it.
@ms-ati: great! only monadic syntax sugar is missing there :)
@Anonymous: your zipperN functions are usually called "uncurry".