Functors, applicatives, arrows, monads and many such abstractions form part of the Scalaz repertoire. It’s no wonder that using Scalaz needs a new way of thinking on your part than is with standard Scala. You need to think more like as if you’re modeling in Haskell rather than in any object-oriented language.
Typeclasses are the cornerstone of Scalaz distribution. Instead of thinking polymorphically in inheritance hierarchies, think in terms of designing APIs for the open world using typeclasses. Scalaz implements the Haskell hierarchy of typeclasses - Functors, Pointed, Applicative, Monad and the associated operations that come with them.
How is this different from the normal way of thinking ? Let’s consider an example from the current Scala point of view.
We say that with Scala we can design monadic abstractions.
flatMap
is the bind which helps us glue abstractions just like you would do with >>=
of Haskell. But does the Scala standard library really have a monad abstraction ? No! If it had a monad then we would have been able to abstract over it as a separate type and implement APIs like sequence in Haskell ..sequence :: Monad m => [m a] -> m [a]
We don’t have this in the Scala standard library. Consider another example of a typeclass,
Applicative
, which is defined in Haskell as class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
…
Here
pure
lifts a
into the effectful environment of the functor, while (<*>)
takes a function from within the functor and applies it over the values of the functor. Scalaz implements Applicative
in Scala, so that you can write the following:scala> import scalaz._
import scalaz._
scala> import Scalaz._
import Scalaz._
scala> List(10, 20, 30) <*> (List(1, 2, 3) map ((_: Int) * (_: Int)).curried)
res14: List[Int] = List(10, 20, 30, 20, 40, 60, 30, 60, 90)
Here we have a pure function that multiplies 2 Ints. We curry the function and partially apply to the members of the
List(1, 2, 3)
. Note List
is an instance of Applicative
Functor. Then we get a List of partial applications. Finally <*>
takes that List
and applies to every member of List(10, 20, 30)
as a cartesian product. Of course the Haskell variant is much less verbose ..(*) <$> [1, 2, 3] <*> [10, 20, 30]
and this is due to better type inference and curry by default strategy of function application.
You can get a more succinct variant in Scalaz using the
|@|
combinator ..scala> List(10, 20, 30) |@| List(1, 2, 3) apply (_ * _)
res17: List[Int] = List(10, 20, 30, 20, 40, 60, 30, 60, 90)
You can have many instances of Applicatives so long you implement the contract that the above definition mandates. Typeclasses give you the option to define abstractions for the open world. Like
List
, there are many other applicatives implemented in Scalaz like options, tuples, function applications etc. The beauty of this implementation is that you can abstract over them in a uniform way through the power of the Scala type system. Just like List
, you can apply <*>
over options as well ..scala> some(10) <*> (some(20) map ((_: Int) * (_: Int)).curried)
res18: Option[Int] = Some(200)
And since all Applicatives can be abstracted over without looking at the exact concrete type, here’s one that mixes an option with a function application through
<*>
..scala> some(9) <*> some((_: Int) + 3)
res19: Option[Int] = Some(12)
The Haskell equivalent of this one is ..
Just (+3) <*> Just 9
Scalaz uses two features of Scala to the fullest extent - higher kinds and implicits. The entire design of Scalaz is quite unlike the usual Scala based design that you would encounter elsewhere. Sometimes you will find these implementations quite opaque and verbose. But most of the verbosity comes from the way we encode typeclasses in Scala using implicits. Consider the following definition of map, which is available as a pimp defined in the trait MA ..
sealed trait MA[M[_], A] extends PimpedType[M[A]] {
import Scalaz._
//..
def map[B](f: A => B)(implicit t: Functor[M]): M[B] = //..
//..
}
map
takes a pure function (f: A => B)
and can be applied on any type constructor M
so long it gets an instance of a Functor[M]
in its implicit context. Using the trait we pimp the specific type constructor with the map
function. Here are some examples of using applicatives and functors in Scalaz. For fun I had translated a few examples from Learn You a Haskell for Great Good. I also mention the corresponding Haskell version for each of them ..
// pure (+3) <*> Just 10 << from lyah
10.pure[Option] <*> some((_: Int) + 3) should equal(Some(13))
// pure (+) <*> Just 3 <*> Just 5 << lyah
// Note how pure lifts the function into Option applicative
// scala> p2c.pure[Option]
// res6: Option[(Int) => (Int) => Int] = Some(<function1>)
// scala> p2c
// res7: (Int) => (Int) => Int = <function1>
val p2c = ((_: Int) * (_: Int)).curried
some(5) <*> (some(3) <*> p2c.pure[Option]) should equal(Some(15))
// none if any one is none
some(9) <*> none should equal(none)
// (++) <$> Just "johntra" <*> Just "volta" << lyah
some("volta") <*> (some("johntra") map (((_: String) ++ (_: String)).curried))
should equal(Some("johntravolta"))
// more succinct
some("johntra") |@| (some("volta") apply (_ ++ _) should equal(Some("johntravolta"))
Scalaz is mind bending. It makes you think differently. In this post I have only scratched the surface and talked about a couple of typeclasses. But the only way to learn Scalaz is to run through the codebase. It's dense but if you like functional programming you will get lots of aha! moments going through it.
In the next post I will discuss how I translated part of a domain model of a financial trade system which I wrote in Haskell (Part 1, Part 2 and Part 3) into Scalaz. It has been a fun exercise for me and shows how you can write your Scala code in an applicative style.
2 comments:
"[...] and this is due to better type inference and curry by default strategy of function application."
And because Scala has - regrettably - no syntactic sugar support for Lists)
Remove the two "List" and it really looks smaller.
Stephan
http://codemonkeyism.com
scalaz is really cool.
Check out embeddedmonads mixing scalaz monads and scala's continuations for implicit monadic code.
The cool thing about monads is, one can even capture computations and analyze/interpret them later. For examples using embeddedmonads and scalaz for good see scala probability DSL
Post a Comment