James Earl Douglas
March 30, 2016
Sanitize potentially invalid data.
name
, email
, and
phone
valuesUser
instance?A functor converts a function A => B
to a function
F[A] => F[B]
.
F[A] ~~~~~~~~► F[B]
│ ▲
└──► A => B ───┘
See the functor laws.
F[_]
is a type constructorF[_]
is parameterized by a single type variable.
There is never a bare instance of F
, but only of
F[A]
for some type A
.
Familiar examples of F[_]
:
List[_]
Option[_]
Try[_]
Either[Exception, _]
case class Box[A](value: A)
Functor[Option]
FunctorOps
enrichmentSometimes* it's impractical to use the function
map(fa)(f)
.
We can pimp a map
method on to the fa
value.
implicit class FunctorOps[A,F[_]:Functor](fa: F[A]) {
def map[B](f: A => B): F[B] =
implicitly[Functor[F]].map(fa)(f)
}
* e.g. for
-comprehensions, infix notation,
OO-style
FlippedFunctorOps
enrichmentOther times*, it's better to start with a function f
and
map it over an argument fa
.
implicit class FlippedFunctorOps[A,B](f: A => B) {
def <%>[F[_]:Functor](fa: F[A]): F[B] =
implicitly[Functor[F]].map(fa)(f)
}
{ x: Int => x * 7 } <%> Option(6) // Some(42)
* It can be easier to read prefix-like notation.
An applicative converts a value F[A => B]
to a
function F[A] => F[B]
.
F[A => B] ~~~~► F[B]
│ ▲
└──► F[A] ───┘
See the applicative functor laws.
Applicative
type classApplicative[Option]
implicit val optionApplicative: Applicative[Option] =
new Applicative[Option] {
def map[A,B](fa: Option[A])(f: A => B): Option[B] =
optionFunctor.map(fa)(f)
def ap[A,B](ff: Option[A => B])(fa: Option[A]): Option[B] =
ff match {
case Some(f) =>
fa match {
case Some(a) => Some(f(a))
case None => None
}
case None => None
}
}
optionApplicative.ap(Option(6))(Option({ x: Int => x * 7 })) // Some(42)
ApplicativeOps
enrichmentAs before, it can be impractical to use the function
ap(ff)(fa)
.
We can pimp an ap
method on to the lifted function
ff
.
ap
for thatap
converts a value F[A => B]
to
F[A] => F[B]
.
What about lifted functions of higher arity, like
F[A => B => C]
?
B
, or not a B
The B
in F[A => B]
is a type
variable.
In practice B
can have any type, even a function like
Y => Z
.
Let's simplify F[A => B => C]
to
F[A => β]
.
ap: F[A => β] => F[A] => F[β] // F[β] == F[B => C]
ap: F[B => C] => F[B] => F[C]
ApplicativeOps
exampleEasy; curry them!
FlippedApplicativeOps
As before, sometimes* it's better to start with a lifted function
ff
and apply it to a lifted argument fa
.
implicit class FlippedApplicativeOps[
A,B,F[_]:Applicative](ff: F[A => B]) {
def <*>(fa: F[A]): F[B] =
implicitly[Applicative[F]].ap(ff)(fa)
}
Option({ x: Int => x * 7 }) <*> Option(6) // Some(42)
* It can be easier to read prefix-like notation.
Validation
sealed trait Validation[E,A]
case class Success[E,A](value: A) extends Validation[E,A]
case class Failure[E,A](value: E) extends Validation[E,A]
So, pretty much the same thing as Either[E,A]
.
Semigroup
A semigroup lets us concatenate stuff.
Semigroup[List[A]]
We'll need a way to collect errors. Let's use lists of strings.
Applicative[Validation[E,_]]
Validation[E,_]
is our F[_]
.
implicit def validationApplicative[E:Semigroup]: Applicative[
({type λ[α] = Validation[E,α]})#λ] =
new Applicative[({type λ[α] = Validation[E,α]})#λ] {
...
}
sbt's kind-projector plugin
can clean this up as Validation[E,?]
Applicative[Validation[E,_]]#map
Applicative[Validation[E,_]]#ap
def ap[A,B](ff: Validation[E,A => B])
(fa: Validation[E,A]): Validation[E,B] =
fa match {
case Success(a) =>
ff match {
case Success(f) => Success(f(a))
case Failure(es) => Failure(es)
}
case Failure(es) =>
ff match {
case Success(f) => Failure(es)
case Failure(es2) => val se = implicitly[Semigroup[E]]
Failure(se.append(es2)(es))
}
}
map
and ap
scala> val newUser = (User.apply _).curried
newUser: String => (String => (String => User)) =
<function1>
scala> val a = Validation.applicative[List[String]]
a: Applicative[[α]Validation[List[String],α]] =
Validation$$anon$1@133c92b
scala> a.map(Success("James"))(newUser)
res0: Validation[List[String],String => (String => User)] =
Success(<function1>)
scala> a.ap(res0)(Success("james@earldouglas.com"))
res1: Validation[List[String],String => User] =
Success(<function1>)
scala> a.ap(res1)(Success("555-123-4567"))
res2: Validation[List[String],User] =
Success(User(James,james@earldouglas.com,555-123-4567))
type Parsed[A] = Validation[List[String], A]
def parseR(n: String, x: String, r: Regex): Parsed[String] =
r.findFirstIn(x) match {
case Some(_) => Success(x)
case _ => Failure(List(s"""invalid ${n}: "${x}""""))
}
parseR("name", "James", """\w+(\s\w+)+""".r)
// Failure(List(invalid name: "James"))
parseR("name", "James Douglas", """\w+(\s\w+)+""".r)
// Success("James Douglas")
ap
case class User(name: String, email: String, phone: String)
def parse(name: String, email: String, phone: String): Parsed[User] =
parseR("name", name, """\w+(\s\w+)*""".r).ap(
parseR("email", email, """[^@]+@[^@]+""".r).ap(
parseR("phone", phone, """\d{3}-\d{3}-\d{4}""".r).map(
(User.apply _).curried
)
)
)
parse("James", "james", "555-JAMES-42")
// List(invalid phone: "555-JAMES-42",
// invalid email: "james")
parse("James", "james@earldouglas.com", "555-123-4567"):
// User(James,james@earldouglas.com,555-123-4567)
<%>
and
<*>
def parse(name: String, email: String, phone: String): Parsed[User] =
(User.apply _).curried <%>
parseR("name", name, """\w+(\s\w+)*""".r) <*>
parseR("email", email, """[^@]+@[^@]+""".r) <*>
parseR("phone", phone, """\d{3}-\d{3}-\d{4}""".r)
parse("James", "james", "555-JAMES-42")
// List(invalid email: "james",
// invalid phone: "555-JAMES-42")
parse("James", "james@earldouglas.com", "555-123-4567")
// User(James,james@earldouglas.com,555-123-4567)
Using Scalaz is pretty much the same as what we've seen so far.
Applicative#ap
ap[A,B](fa: F[A])(f: F[A => B]): F[B]
Functor
and Applicative
type
classes:
Validation
data type:
Semigroup[List]
instance:
Again, using Cats is pretty much the same as what we've seen so far.
Validation
with Validated
Success
with Valid
Failure
with Invalid
Functor
and Applicative
type
classes:
Validated
data type:
Semigroup[List]
instance:
Validation
in Scalaz: eed3si9n.com/learning-scalaz/Validation.html
Validated
in Cats: typelevel.org/cats/datatypes/validated.html
<*>
in Haskell: hackage.haskell.org/package/base-4.8.2.0/docs/Control-Applicative.html#v:-60--42--62-
<$>
in Haskell: hackage.haskell.org/package/base-4.8.2.0/docs/Data-Functor.html#v:-60--36--62-