I have read some material describing Computation Expressions and knew computation expressions are the F# implementation of monads, but I did know why one should use a monad.
A few days ago saw the video of presentation by Robert C. Martin (Uncle Bob) at the Norwegian Developers Conference called “WTF is a Monad” and now it makes sense to me.
It is a proven way of solving problems. If you:
- have a problem in one domain
- you can transform it to another domain
- solve the problem in the other domain
- and can transform it back
you have a solution.
I have translated some of the code of the presentation to F#. I tried to stay as close to the original code as possible and did not do much refactoring. I skipped the last two examples (distributions an state).
Update: I did not implement the examples that required lift. I have not figured out how to do that. I have created the lift myself (details: http://bugsquash.blogspot.com/2010/12/notes-on-haskell-functors-and-f.html)
I like to thank Robert for the permission to share the code. Feel free to add improvements in the comments.
//Non-Monadic dots
let dotsToN (d:string) =
d.Length
let result1 = dotsToN "....."
let nToDots (n:int) =
new string('.', n)
let result2 = nToDots 5
let addDots da db =
let a = dotsToN da
let b = dotsToN db
nToDots (a+b)
let result3 = addDots "..." "..."
//Monadic dots
let dotResult = nToDots
let dotBind d (f:int -> string) =
d |> dotsToN |> f
let addDots' da db =
dotBind da (fun a ->
dotBind db (fun b ->
dotResult (a + b)))
let result4= addDots' "..." "..."
//dot-Monad
type dotBuilder()=
member x.Bind(d,(f:int -> string)) = d |> dotsToN |> f
member x.Return(n) = new string('.', n)
let dot = new dotBuilder()
let addDots'' da db =
dot { let! x = da
let! y = db
return x + y
}
let result5= addDots'' "..." "..."
let multiplyDots da db =
dot { let! x = da
let! y = db
return x*y
}
let result6= multiplyDots "..." "..."
let dcd dt du =
dot { let! tens = dt
let! units = du
return (10 * tens + units)
}
let result7= dcd "..." "....."
// In Clojure the monad gets the lift for free
// In F# we have to create it ourselves
// More info: http://bugsquash.blogspot.com/2010/12/notes-on-haskell-functors-and-f.html --
//
let liftDots_1 f a1 =
dot {
let! x1 = a1
return f x1
}
let liftDots_2 f a1 a2 =
dot {
let! x1 = a1
let! x2 = a2
return f x1 x2
}
let liftDots_4 f a1 a2 a3 a4 =
dot {
let! x1 = a1
let! x2 = a2
let! x3 = a3
let! x4 = a4
return f x1 x2 x3 x4
}
let substractDots = liftDots_2 (-)
let result7a= substractDots "....." ".."
let mean4 a b c d = (a+b+c+d)/4
let dmean4 = liftDots_4 mean4
let result7b= dmean4 "..." ".." "..." "...."
//
//complex number (inspection)
//
open System.Numerics
let c_0 = Complex.Zero
let c_1 = Complex.One
let c_i = Complex.ImaginaryOne
type complexBuilder()=
member b.Return(x:float) = new Complex(x,0.0)
member b.Bind((c:Complex),(f:float -> Complex)) =
if c.Imaginary = 0.0 then
f c.Real
else
failwithf "Imaginary"
let complex = new complexBuilder()
let addComplex c1 c2 =
complex { let! x = c1
let! y = c2
return x + y
}
let result8= addComplex c_1 c_1;;
let result9= addComplex c_1 c_i;; //System.Exception: Imaginary
let result10= addComplex c_i c_i;; //System.Exception: Imaginary
//null
type noNullBuilder()=
member b.Return(mv) = mv
member b.Bind(mv,f) = if (mv = null) then null else f mv
let nuNull = new noNullBuilder()
// int can not be null in .Net
let fragile (a:string) (b:string) (c:string) =
if (a= null||b=null||c=null)
then
"CRASH"
else
a + b + c
let result11 = fragile "a" "b" "c"
let result12 = fragile "a" null "c" //"CRASH"
let safeFragile (a:string) (b:string) (c:string) =
nuNull { let! x = a
let! y = b
let! z = c
return x + y + z
}
let result13 = safeFragile "a" "b" "c"
let result14 = safeFragile "a" null "c" //val result14 : string = null
//List
let l_a = [1;2;3]
let l_b = [4;5;6]
let addList = seq {for a in l_a do
for b in l_b do
yield a+b
}|> Seq.toList
let multList = seq {for a in l_a do
for b in l_b do
yield a*b
}|> Seq.toList
//helper for the demo
let flatten l =
let rec flatten' l result =
match l with
|[] -> result
|h::t -> flatten' t (h@result)
flatten' (l|>List.rev) []
let result15 = flatten [[1;2;3];[7;8;9]]
type listBuilder()=
member b.Return(v) = [v]
member b.Bind(mv,f) = mv |> List.map f |> flatten
let list = new listBuilder()
let addList' a b = list {
let! x = a
let! y = b
return x + y
}
let result16 = addList' l_a l_b
let multList' a b = list {
let! x = a
let! y = b
return x * y
}
let result17 = multList' l_a l_b
//distribution example see Expert F# of Don Syme page 239..244
//state http://fsharpcode.blogspot.com/2008/12/f-state-monad-type-state-state-state-of.html
3 comments:
I haven't seen the presentation so I don't know what Robert does with lift, but I can tell you that lift (liftM when restricted to monads) is just a 'map' function. You can trivially translate the Haskell definition of liftM and use it in F# ... you can't define it generically as in Haskell though.
BTW the link to by blog post isn't right... and now that I re-read it I noticed I already defined lift there :-)
If you have trouble with this I recommend posting a question on stackoverflow...
Thanks Mauricio for the comments.
I have updated the code and the link.
Post a Comment