From Josh Suereth's "Scala in Depth":
"Applicative functors provide a way to take two computations and join them together using a function. The Traversable example highlights how two collections can be parallelized into pairs. Applicative functors and parallel processing go together like bread and butter."
I have a vague idea of the whole functors/monads/applicative stuff, but not exactly a strong grasp of it (new to the whole monad, functor stuff). I understand a bit of the concept of monads (flatten, flatMap) and monadic workflow, and functors (maps).
Can anyone please elaborate for me in terms of how it's done, examples, and/or benefits of it versus "traditional" parallelization?
Applicative is a generalization of Monad , allowing expression of effectful computations in a pure functional way. Applicative is generally preferred to Monad when the structure of a computation is fixed a priori. That makes it possible to perform certain kinds of static analysis on applicative values.
Functors apply a function to a wrapped value: Applicatives apply a wrapped function to a wrapped value: Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.
A functor takes a pure function (and a functorial value) whereas a monad takes a Kleisli arrow, i.e. a function that returns a monad (and a monadic value). Hence you can chain two monads and the second monad can depend on the result of the previous one.
I forwarded the question to Josh Suereth. This is his reply:
Mike -
I don't have a lot of time to respond, but I'll offer to examples of what I mean:
Example #1 - Form Validation.
I want to run some validation against input and aggregate all the errors, i.e. detect them in parallel. With applicative functions I can do so.
So, given a set of "processing" functions, like so:
def processUser(data: Data): Validation[User] = { if (data get "username" isEmpty) Failure("username must not be empty") else { val Some(user) = data get "username" if (user contains badCharacterRegex) Failure(s"username must not contain one of ${badchars}") else Success(user) } } def processCreditCard(data: Data): Validation[CreditCard] = ... def processAddress(data: Data): Validation[Address] = ... def handleForm(data: Data): ??? = { (processUser(data), processCreditCard(data), processAddress(data)) map { (user, card, address) => postPayment(user, address, card) } recover { (errors) => errors foreach println }
Now handle form will print out errors with CreditCard/username + address all at the same time, since you've combined them using an applicative functor. That's parallel error reporting (although testing isn't actually done in parallel).
(2) Futures
I want to do a few things in parallel and combine results. Future's "zip" method is actually an applicative functor in disguise. I can do this:
Future(computation1) zip Future(computation2) map { case (one,two) => .... }
I've just used Applicative Functors to "join" parallel computations.
It's exactly the same as the Form validation example.Hope that helps! - Josh
(note these code snippets are non-compilable examples; I was using SBT's applicative syntax with the concepts in Scalaz, so you need to choose a library to use applicatives and what they are applying onto)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With