Skip to content

Instantly share code, notes, and snippets.

@bvenners
Forked from tonymorris/scalaz.scala
Last active January 2, 2016 07:59
Show Gist options
  • Save bvenners/8273112 to your computer and use it in GitHub Desktop.
Save bvenners/8273112 to your computer and use it in GitHub Desktop.
// Questions about \/ and Validation
object scalaz {
/*
Thanks for the great explanation, Tony. This explanation as well as the answers
that came back from Runar and Mark nudged my thinking in a different direction.
The feeling I get is that there's a monad inside of Validation trying to get out.
It looks like Validation would be monadic just from its shape, and users seem to
want to use them in for expressions. I now see that a Validation monad would need
to have an applicative personality that is consistent with monad, and I think it
could be done by offering a lower priority Monad instance for Validation (complete
with the default, inherited Applicative behavior that short-circuits on the first
error) as well as a higher priority Applicative instance that accumulates errors.
That way if a method abstracts over Monad, you could pass a Validation to it, but
it would short-circuit if they sequenced it or did anything else that accessed
its Applicative nature. If a method abstracts over Applicative, you could (as you can
now) pass a Validation to it, and it would accumulate.
The reason you might not want to do that is it may be more confusing than any
benefit it provides. It might be simpler for user if there's one type, \/, that
is a monad with consistent applicative behavior and similar type Validation that
is not a monad (including not having a flatMap method so it can't be used monadically
in a for expression) and has an applicative that accumulates.
The reason this works for Or, I think, is that Or isn't tied into scalaz-like type
classes. It is just a functional/OO design that has an API that allows it to
be treated monadically, and another API when the bad type is an Every that allows
accumulation.
But just for fun, I decided to try it and I got it to work. It was a bit tricky to
get the Scala compiler to understand the implicit priorities I wanted. I improved
my understanding of how that works in the process, and will explain that below.
This is a compilable source file with revisions
available at https://gist.github.com/bvenners/8273112
That's a fork of Tony's gist. I have removed his comments and replaced
them with my own.
*/
/*
All of the code until my next comment is unchanged from Tony's original gist, except
with Tony's comments removed.
*/
trait Applicative[F[_]] {
def pure[A](a: A): F[A]
def ap[A, B](f: F[A => B], a: F[A]): F[B]
}
trait Monad[F[_]] extends Applicative[F] {
def unit[A](a: A): F[A]
def bind[A, B](f: A => F[B], a: F[A]): F[B]
override def pure[A](a: A): F[A] =
unit(a)
override def ap[A, B](f: F[A => B], a: F[A]): F[B] =
bind((ff: A => B) => bind((aa: A) => pure(ff(aa)), a), f)
}
trait Semigroup[A] {
def op(a1: A, a2: A): A
}
def Applicative_\/[X]: Applicative[({type λ[α]=X Either α})#λ] =
new Applicative[({type λ[α]=X Either α})#λ] {
def pure[A](a: A) =
Right(a)
def ap[A, B](f: X Either (A => B), a: X Either A) =
for {
ff <- f.right
aa <- a.right
} yield ff(aa)
}
def Applicative_Validation[X](s: Semigroup[X]): Applicative[({type λ[α]=X Either α})#λ] =
new Applicative[({type λ[α]=X Either α})#λ] {
def pure[A](a: A) =
Right(a)
def ap[A, B](f: X Either (A => B), a: X Either A) =
f match {
case Left(x1) =>
a match {
case Left(x2) =>
Left(s.op(x1, x2))
case Right(_) =>
Left(x1)
}
case Right(ff) =>
a match {
case Left(x2) =>
Left(x2)
case Right(aa) =>
Right(ff(aa))
}
}
}
def Monad_\/[X]: Monad[({type λ[α]=X Either α})#λ] =
new Monad[({type λ[α]=X Either α})#λ] {
def unit[A](a: A) =
Right(a)
def bind[A, B](f: A => X Either B, a: X Either A) =
a.right flatMap f
}
case class Or[A, B](run: A Either B) {
// the \/ monad
def flatMap[X](f: B => A Or X): A Or X =
Or(run.right.flatMap(f(_).run))
// the \/ applicative
def ap_\/[X](f: A Or (B => X)): A Or X =
Or(
for {
ff <- f.run.right
aa <- run.right
} yield ff(aa)
)
// the validation applicative
def ap_Validation[X](f: A Or (B => X))(implicit s: Semigroup[A]): A Or X =
Or(
f.run match {
case Left(x1) =>
run match {
case Left(x2) =>
Left(s.op(x1, x2))
case Right(_) =>
Left(x1)
}
case Right(ff) =>
run match {
case Left(x2) =>
Left(x2)
case Right(aa) =>
Right(ff(aa))
}
}
)
}
def sequence[F[_], A](x: List[F[A]])(implicit F: Applicative[F]): F[List[A]] =
x.foldRight[F[List[A]]](F.pure(Nil))((a, b) =>
F.ap(F.ap(F.pure((a: A) => (as: List[A]) => a :: as), a), b))
implicit def Monad_Or[X]: Monad[({type λ[α]=X Or α})#λ] =
new Monad[({type λ[α]=X Or α})#λ] {
def unit[A](a: A) =
Or(Right(a))
def bind[A, B](f: A => X Or B, a: X Or A) =
a flatMap f
}
val list =
List(Left(List(7)), Left(List(8)), Right("abc"), Right("def"))
def hithere =
sequence[({type λ[α]=List[Int] Or α})#λ, String](
list map (Or(_))
)
case class Validation[A, B](run: A Either B) {
def flatMap[X](f: B => A Validation X): A Validation X =
Validation(run.right.flatMap(f(_).run))
}
/*
Here's where the code first departs from Tony's original. I actually
define a Monad instance for Validation, but I put it in a trait.
Note that as a Monad, Validation requires no Semigroup constraint.
*/
trait MonadicValidation {
implicit def Monad_Validation[X]: Monad[({type λ[α]=X Validation α})#λ] =
new Monad[({type λ[α]=X Validation α})#λ] {
def unit[A](a: A) =
Validation(Right(a))
def bind[A, B](f: A => X Validation B, a: X Validation A) =
a flatMap f
}
}
/*
I then put Tony's method unchanged providing the accumulating Applicative for
Validation in Validation's companion object, and made that extend the trait
that holds the implicit providing the monad instance for Validation. Here is
where I ran into an unexpected ambiguous implicit compiler error. I didn't fully
understand the point system that the compiler uses to select implicits. Turns out you
get points for being more specific as well as points for being in subtypes, and
these points are added together.
Monad is more specific than Applicative because Monad is a subtype of
Appicative. So if placed in the same trait Monad_Validation scores one
more point than Applicative_Validation_for_realz when the compiler is looking
for an Applicative. The compiler then awards Applicative_Validation_for_realz a point
for being in a sub-object, and that evens the score, so the compile fails with an
ambiguity error. What I ended up doing to solve it is making this clunky subtrait
of Applicative, MoreSpecificApplicative, that does nothing more than cause the compiler
to give it a point for being more specific. That evens that score, so the compiler will
pick the accumulating one if X is a Semigroup and an Applicative is asked for, because
it is declared in a subobject.
*/
object Validation extends MonadicValidation {
trait MoreSpecificApplicative[F[_]] extends Applicative[F]
implicit def Applicative_Validation_for_realz[X](implicit s: Semigroup[X]): MoreSpecificApplicative[({type λ[α]=X Validation α})#λ] =
new MoreSpecificApplicative[({type λ[α]=X Validation α})#λ] {
def pure[A](a: A) =
Validation(Right(a))
def ap[A, B](f: X Validation (A => B), a: X Validation A) =
Validation(
f.run match {
case Left(x1) =>
a.run match {
case Left(x2) =>
Left(s.op(x1, x2))
case Right(_) =>
Left(x1)
}
case Right(ff) =>
a.run match {
case Left(x2) =>
Left(x2)
case Right(aa) =>
Right(ff(aa))
}
}
)
}
}
implicit def ListSemigroup[A]: Semigroup[List[A]] =
new Semigroup[List[A]] {
def op(a1: List[A], a2: List[A]) =
a1 ::: a2
}
/*
This works the same as before: the compiler picks the higher priority
impicit and you get accumulation. The result is: Validation(Left(List(7, 8)))
*/
def hithereagain =
sequence[({type λ[α]=List[Int] Validation α})#λ, String](
list map (Validation(_))
)
/*
To demonstrate what happens when a Validation is treated as a monad, I made
this sequenceAsMonad method. It is the exact same implementation as Tony's
original sequence, but it requires a Monad instead of an Applicative.
*/
def sequenceAsMonad[F[_], A](x: List[F[A]])(implicit F: Monad[F]): F[List[A]] =
x.foldRight[F[List[A]]](F.pure(Nil))((a, b) =>
F.ap(F.ap(F.pure((a: A) => (as: List[A]) => a :: as), a), b))
/*
When I call this method, it passes in the Monad instance, and uses the
Applicative inherited by monad, so it short-circuits. The result is: Validation(Left(List(7)))
*/
def hithereyetagain =
sequenceAsMonad[({type λ[α]=List[Int] Validation α})#λ, String](
list map (Validation(_))
)
/*
One way this makes Validation more general (and possibly also more confusing for users)
is that it will still work when an Applicative is asked for but the Left type is not
a Semigroup. In this case, the higher priority Applicative_Validation_for_realz does
not apply at all, because X is not a Semigroup, so it picks the Monad one, which
doesn't accumulate. Although Vector is a semigroup in essence, it isn't one here because
we haven't defined a Semigroup instance for Vector. So a Vector will work to demonstrate:
*/
val listOfNonSemigroups =
List(Left(Vector(7)), Left(Vector(8)), Right("abc"), Right("def"))
/*
Even though we are passing a Validation to a method that abstracts
over Applicative, because no Semigroup is available for Vector, it
picks the Applicative that makes sense more generally, which is the
one supplied by Monad. This results in: Validation(Left(Vector(7)))
*/
def hithereonelasttime =
sequence[({type λ[α]=Vector[Int] Validation α})#λ, String](
listOfNonSemigroups map (Validation(_))
)
/*
This prints:
Or(Left(List(7)))
Validation(Left(List(7, 8)))
Validation(Left(List(7)))
Validation(Left(Vector(7)))
*/
def main(args: Array[String]) {
println(hithere)
println(hithereagain)
println(hithereyetagain)
println(hithereonelasttime)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment