Skip to content

Instantly share code, notes, and snippets.

@debasishg
Created October 4, 2011 14:58
Show Gist options
  • Save debasishg/1261857 to your computer and use it in GitHub Desktop.
Save debasishg/1261857 to your computer and use it in GitHub Desktop.
a brief rant about |@| in scalaz
/**
* |@| is a helper function that helps you accumulate applicative functors. It gives you an ApplicativeBuilder (it's part of
* the implementation though) through which you accumulate your applicatives just as you would using the Builder pattern of
* the GoF in the OO world. Once you have all the applicatives you can pass it a function that will be applied to all the
* values that you have accumulated so far. e.g.
*/
scala> (1.some |@| 2.some) apply {_ + _}
res1: Option[Int] = Some(3)
/**
* Here we accumulate 2 Option values and then apply the summation over the accumulated applicatives. Note the arity of the
* function needs to match the number of applicatives you accumulate.
*
* Typical to scalaz, |@| is pimped into all type constructors for which applicatives are defined. e.g. List, Option etc.
* Hence you can do the same with Lists as well ..
*/
scala> val a, b = List(1)
a: List[Int] = List(1)
b: List[Int] = List(1)
scala> (a |@| b) (_ + _)
res2: List[Int] = List(2)
/**
* So the main excitement about such builders is that they give you a nice declarative syntax with infix notation and
* abstracts the heavy duty stuff behind very cool abstractions like Functors and Applicatives. And the best part is that
* they encourage you to write nice compositional code.
*
* Have a look at this snippet that chains a series of validations on a domain object through |@| ..
*/
// using Validation as an applicative
// can be combined to accumulate exceptions
def makeTrade(account: Account, instrument: Instrument, refNo: String, market: Market,
unitPrice: BigDecimal, quantity: BigDecimal) =
(validUnitPrice(unitPrice).liftFailNel |@|
validQuantity(quantity).liftFailNel) { (u, q) =>
Trade(account, instrument, refNo, market, u, q)
}
/**
* Here's a summary of what we do above. Validates 2 fields of the domain model Trade and if the validations pass then a
* concrete Trade object is created. Otherwise *all* errors are accumulated and lifted into a Non Empty List. Try doing this
* with a typical imperative statement based syntax. You will have a much more verbose syntax and bigger surface area of
* your abstraction.
*
* All |@| does is accumulate Applicatives and apply the accumulated parts to the function that you provide. But I said
* compositional :-) .. hence |@| composes nicely with other functional abstractions. Since |@| is pimped into an
* Applicative, we define Validation as an instance of Apply typeclass - so Validation plays nicely with |@|. And this
* extension is not closed. You can define your own abstractions, implement them as instances of the typeclass and use |@|
* to chain them.
*/
/**
* >>>> Question: What I don't follow is what that function gets (?) in case validation fails and the error-list is
* produced.
*
* Validation is an Applicative Functor. Here we are composing a few of them (each individual Validation object) to form
* another Applicative. So what we get finally is an Applicative. Now note the methods map and flatMap in the trait
* Validation:
*/
def map[B](f: A => B): Validation[E, B] = this match {
case Success(a) => Success(f(a))
case Failure(e) => Failure(e)
}
def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = this match {
case Success(a) => f(a)
case Failure(e) => Failure(e)
}
/**
* These are the bind methods that do the chaining of the Validation instances.
*
* The passed in function gets invoked *only* in the success case. And the final applicative is a success only when the
* individual validations are successes. So the function is not invoked at all if any of the validations fail.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment