I think the key take away from the post, which should help reason about applicative functors is this sentence:
Hey, so you got your function wrapped into a bit of a context, huh?
Not to worry, I know how to apply those kind of wrapped functions
Applicative provides a way of applying a function wrapped in a context over other wrapped values.
|@|
and the underlying type ApplicativeBuilder
are just scalazs way of constructing these applicatives via a DSL.
From the documentation (emphasis mine):
Whereas a scalaz.Functor
allows application of a pure function to
a value in a context, an Applicative also allows application of a
function in a context to a value in a context (ap
)
Operator |@| is the product operation
By "product operation" the OP means that it is the operation which takes two values and wraps them inside the ApplicativeBuilder
.
|@|
is a method that when invoked, returns an instance of ApplicativeBuilder
:
final def |@|[B](fb: F[B]) = new ApplicativeBuilder[F, A, B] {
val a: F[A] = self
val b: F[B] = fb
}
Where F
is a first order kind which has an Apply
instance defined:
implicit val F: Apply[F]
Where Apply
is just an Applicative
without the point
method.
combining your applicatives into a product by using |@| results in an
ApplicativeBuilder which takes a function to perform on the product
(since product + map is a very common use case)
If you we take your example and simplify it a bit for two Option[Int]
s:
import scalaz.Scalaz._
val o1 = 1.some
val o2 = 1.some
val result: ApplicativeBuilder[Option, Int, Int] = o1 |@| o2
val finalRes: Option[Int] = result.apply(_ + _)
We:
- Apply
|@|
to two instances an of Option[Int]
and get back an ApplicativeBuilder[Option, Int, Int]
. Option
here is our F
, which has an instance of Apply
.
- After getting back an instance of the builder, we invoke it's
apply
method. We provide it with a function of shape Int -> Int
and it gives us back a Option[Int]
, meaning we are still inside the context, but with the operation applied to our values.