The first blog post about Functors and co. introduced the three basic function transformations that form Functor, Monad and Applicative.
a) ( A=>B ) => ( C[A]=>C[B] ) | Functor
b) ( A=>C[B] ) => ( C[A]=>C[B] ) | Monad
c) ( C[A=>B] ) => ( C[A]=>C[B] ) | Applicative
In the last post in this series I used the map function as a demonstration for different styles to implement the three concepts. Let’s now stay a bit longer with our Functor, play a bit with map and look what we detect.
Although these patterns are not metaphors, I will neverless begin to think a little bit metaphorically about a Functor – or more precisely: about our MyBox.
In the examples so far we have seen the class MyBox as a carrier of a value:
class MyBox[T](val value:T)
And we have also seen implementations like the following:
def map[A,B]( rawfunc:A=>B ) : MyBox[A]=>MyBox[B] = (a:MyBox[A]) => new MyBox( rawfunc(a.value) )
n.map( (a:String)=>a.length ).value // -> 5
Notice here the access to the box’s value with a.value . While the first example, map, encapsulates this access in its implementation, it is done in the open in the last example.
But an important fact about Functors et al is exactly this: The value is hidden inside the Functor wrapper, and access to it is only possible by passing a function to some sort of “guardian” (map, flatMap). This “guardian” has the control over the Functor, and he “grants” the access to the value to the “visitor”, i.e. the given function.
It is as if the value will never get out of the Functor, but stay inside. So, if you want to cause any effect on it, you have to pass a transformation into the functor. The result of the effect stays in the Functor.
Another metaphor is to think of a Functor wrapper as a test tube. You have some dark blue liquid in it. Then you open the tube and give some reagent into it, and after a short while the dark blue liquid is transformed into a light green sort of sirup. All that happened inside the test tube.
So now we have our MyBox, containing a string value, and we open the lid, throw a function into it, and now the value and the function react, resulting in an integer value inside that MyBox.
Ahem, ok, so far for the metaphors. The thing is, that our MyBox is a nice wrapper for demonstration, but it is absolutely useless. There is no reason to keep the value inside, and no reason why we shouldn’t do something like this:
val boxedstring = new MyBox("Hello") val hellolenght = boxedstring.value.length
But let’s assume that instead of this boring MyBox the wrapper would be more interesting. It could be a collection, like List. Then applying the function to every element and collect the results in another List would be a tedious task. We would have to externally iterate over the elements. The map function does this for us internally. Or consider the Option class. It is very much like our MyBox, but it has some kind of semantics. And transforming the value via map plus a given function keeps this semantic. That is, when used seriously, a Functor is not such a dull thing like MyBox, but provides some interesting information or functionality. It is a context around the value.
Ok, I digressed a bit, so let us come back to map: ( A=>B ) => ( C[A]=>C[B] )
The map function allows us to access the value inside a wrapper C[_], in our case MyBox, and allows us to transform not only the value itself, but also the type of the value. We saw that in our example, where the string value “Hello” in the MyBox was transformed into an int value representing the length.
If one starts to work with generics and type parameters, one can easily be confused by the meaning of a type parameter like A or B. These two letters each simply stand for a “type”, but there is no statement about what this type is. It can be a simple type, like String or Int in our example, but it can also be a complex generic type.
For example, the result type B could be a List[String]:
val boxedstring = new MyBox("Hello") val splitter = (a:String) => a.split("").toList map(splitter)( boxedstring ) // Result: MyBox[List[java.lang.String]]
Splitter is a function of type String=>List[String], and according to how map works, the overall result is of type MyBox[List[String]]. You can imagine that this works for many other types too, like e.g. Option, so given a function of type String=>Option[Int] we would get a MyBox[Option[Int]].
Now let’s do a funny thing:
val boxhead = (a:String) => new MyBox(a.head) map(boxhead)( boxedstring ) // Result: MyBox[MyBox[Char]]
Wow, two things are interesting here:
- The result type is a MyBox containing a value of type MyBox[Char] – we have a box in a box!
- The function type of function boxhead is String=>MyBox[Char]
The general signature of map is (A=>B), in this case it is (String=>MyBox[Char]).
But we already have a function with the general signature A=>C[B], which in our case would be A=>MyBox[B]. That is the signature of flatMap !
It seems that flatMap is only a special case of the more general map. There is a relationship between Functor and Monad, and we will take a closer look to flatMap in the next article.
For now let’s try something else. Until now we have only worked with functions that take one parameter. But what happens if we have one value but a function that takes two? That is where currying comes into play: Each function taking two parameters and returning a result can also be expressed as a function taking one parameter and returning another function which takes the second parameter and then returns the result:
scala> val add = (a:Int, b:Int) => a + b // function with two parameters returns result add: (Int, Int) => Int = <function2> scala> add(3,5) res13: Int = 8
is equivalent to
scala> val add = (a:Int) => (b:Int) => a + b // function with one parameter returns function add: Int => (Int => Int) = <function1> scala> val add3 = add(3) // new function based on argument 3 add3: Int => Int = <function1> scala> add3(5) // function returns result res15: Int = 8
More generally: Every function taking more than one parameters can be expressed as a chain of functions taking one parameter and returning a function which also takes one parameter. That should not be new to you, as we used that technique in the last post to transform the map function from one implementation style to another.
Now back to map. As map takes a function with only one parameter, each function with more than one parameter has to be changed into its curried form.
A function with two parameters
val conc = (a:String, b:String) => a + b
is changed to its curried form.
val conc = (a:String) => (b:String) => a + b
Now let’s try what happens when we put that into our map for boxedstring.
map(conc)(boxedstring) // Result: MyBox[String => String]
Wow, and suddenly we have a function inside a box! It seems here we are at the fundaments of Applicative, as the result type is exactly the signature of the apply function.
We talked a bit about what a Functor wrapper like MyBox in practice can be, and we played a bit with the map function. Perhaps this encourages you to experiment more with it.
So far for now, and happy mapping. In the next post we will play a bit with flatMap.