After the discussion about Monads I decided to try to implement a Monad in Scala, eventually without copying from other Monads. My idea was to write something link Option or Try, so a container of a single, optional, value. I started by defining a trait with all the required methods:
- println("flatmapping")
- bind(func)
- }
- println("mapping")
- flatMap (x => MonadTrait(f(x)))
- }
- println("invoking withFilter")
- filter(x => func(MonadTrait(x)) )
- }
- def get: T
- }
As you can see, the normal derived functions are already defined in the trait. Moreover I added a println in each method to know which one is invoked. Started from this I added two classes, one for the monad with a value and the other with an empty monad. I used companion objects for the apply methods.
- println(s"$unit: binding")
- func(unit)
- }
- println(s"$unit: invoking foreach")
- f(unit)
- }
- println(s"$unit: filtering")
- }
- println(s"$unit: invoking withFilter")
- filter(x => func(MonadTrait(x)) )
- }
- println(s"$unit: getting value")
- unit
- }
- }
- println("EmptyMonad: binding")
- EmptyMonad()
- }
- println("EmptyMonad: mapping")
- EmptyMonad()
- }
- println("EmptyMonad: foreach")
- }
- println("EmptyMonad: filtering")
- EmptyMonad()
- }
- println("EmptyMonad: invoking withFilter")
- EmptyMonad()
- }
- println("EmptyMonad: getting value")
- }
- }
- object MonadTrait {
- }
- }
- }
- object EmptyMonad {
- }
- object Monad {
- }
Well, now it looked exactly as a Monad. Of course it sounds a bit strange to use a Monad (Option) to check which Monad we want to create, but those details are useless, we want to unleash the power of Monads, not to care about details.
As I was saying in the other post, flatMap is my bind operator. I defined a bind operator and I invoked it in the flatMap only for making it explicit. Map as per definition is relaying on flatMap. We have filters operators, because at the end we want to invoke the Monads by a for-comprehension. The two classes are then implementing the different functions in the expected way, by also logging every method call.
So, let’s create our test. Here the code:
We don’t use the stupidFunc yet, it was created to force the for-comprehension to flatMap our result. The result of running our test is:
- 1: getting value
- 2: getting value
- [...]
- 9: getting value
- 10: getting value
- 6: invoking foreach
- 6
- [...]
- 10: invoking foreach
- 10
So, we have the get printed from line 5 of the test (if x.get > 5) and the invoking foreach of the c <- a. What I did after this is to try force the flatmap. For doing so I was adding a level of nesting with the stupidFunc. The executable becomes
- yield x
- println(test)
- }
This leads to a compiler error:
Error:(15, 10) type mismatch; found : MonadTrait[MonadTrait[Int]] required: scala.collection.GenTraversableOnce[?] b <- stupidFunc(a)
This because apparently the definition of flatMap used in the for comprehension expects you to use the standard definition of flatMap in the Traversable hierarchy that is the follow:
def flatMap [B, That] (f: (A) ⇒ GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That
for example I could have defined my Monad by extending FilterMonadic. This has the exact definition of map, flatMap, forEach and withFilter used for defining a Monad.
For today it is enough. Stay tuned!
Leave a Reply