M.C. Oscar Vargas Torres
@oscarvarto
Scala Developer and
(dys)functional programmer
case class Person(name: String, age: Int)
// Bad input
val unrealPerson = Person(" ", -30)
Take a look at Refined documentation
// A person must have a NonEmpty name and a non-negative age.
object RefinedExample {
type AgeAux = Interval.Closed[W.`0`.T, W.`127`.T]
case class Age(n: Int Refined AgeAux)
type NameAux = NonEmpty And Not[Forall[Whitespace]]
case class Name(value: String Refined NameAux)
case class Person(name: Name, age: Age)
val spiderman = Person(Name("Peter Parker"), Age(25))
}
import scalaz.syntax.std.string.ToStringOpsFromString
object stringsyntax {
implicit class StringExtraOps(s: String) {
/** Returns true if not empty and every character is whitespace
*
* @note using scala APIs
*/
def isWhitespaceOnly: Boolean =
s.nonEmpty && s.forall(_.isWhitespace)
}
}
Now, in the call site:
import stringsyntax.StringExtraOps
val name = " "
// call site
val b = name.isWhitespaceOnly
package semigroupexamples
object Age {
val MaxAge: Int = 127
sealed trait AgeError
case object AgeIsNegative extends AgeError
case object AgeIsTooBig extends AgeError
def apply(age: Int): Either[AgeError, Age] = age match {
case a if a < 0 => Left(AgeIsNegative)
case a if a > MaxAge => Left(AgeIsTooBig)
case a => Right(new Age(a){})
}
}
sealed abstract case class Age private(value: Int)
For details check:
sealed trait NameError
case object NameCannotBeEmpty extends NameError
case object NameCannotBeWhitespaceOnly extends NameError
Either[NonEmptyList[NameError], Name]
trait ValidationError
sealed trait AgeError extends ValidationError
// same age error cases as before.
sealed trait NameError extends ValidationError
// same name error casas as before
Either[NonEmptyList[ValidationError], Person]
Could model
Name
and an Age
(and others!), orPerson
type Validated[A] = Either[Error, A]
def p(name: Validated[Name],
age: Validated[Age]): Validated[Person] = for {
n <- name
a <- age
} yield Person(n, a)
The big advantage of restricting to Applicative is that
Validation
is explicitly for situations where we wish to report all failures
6.7.3 Validation of the book Functional Programming for Mortals with Scalaz
Monoid are more than intellectual curiosities.
Make sure to read the contents for this module.
A Semigroup can be defined for a type if two values can be combined. The operation must be associative.
A Monoid is a Semigroup with a zero element (also called empty or identity).
MonoidExample1
uses
import scalaz._
import Tags.Multiplication
import Scalaz._
object MonoidExample1 {
val x1: Int = 1 |+| 2
// To use a different binary operation, use a tagged type
val x2: Int @@ Multiplication =
Multiplication(1) |+| Multiplication(2)
}
Option[A] @@ Multiplication
monoid
import scalaz._
import Tags.Multiplication
import Scalaz._
object MonoidExample2 {
type MultOption[A] = Option[A] @@ Multiplication
implicit def optionMult[A]
(implicit ev: Monoid[Option[A]]): Monoid[MultOption[A]] =
new Monoid[MultOption[A]] {
def zero: MultOption[A] = Tag(None)
def append(f1: MultOption[A],
f2: => MultOption[A]): MultOption[A] =
Tag(Tag.unwrap(f1) |+| Tag.unwrap(f1))
}
val x2: MultOption[Int] =
Multiplication(1.some) |+| Multiplication(2.some)
}
Leveraging Scalaz' LastMaybe[A]
monoid:
import scalaz._
import Maybe._
import Scalaz._
object MonoidExample3 {
val x1: LastMaybe[Int] =
1.just.last |+| 2.just.last
val ns: EphemeralStream[LastMaybe[Int]] =
(1 |=> 5).map(_.just.last)
val lastN: LastMaybe[Int] =
ns.fold
val x2: Maybe[Int] @@ Tags.Last =
empty.last
}
Monoids instances using
Boolean
sBoolean
sobject MonoidExample4 {
// Disjunction: Or
val b2: Boolean @@ Disjunction =
true.disjunction |+| false.disjunction
// Conjunction: And
val b4: Boolean @@ Conjunction =
true.conjunction |+| false.conjunction
}
trait ValidationRules[A] {
type E <: ValidationError
type Rule = Reader[A, Validated[A]]
def must(predicate: A => Boolean, error: E): Rule =
Reader { a =>
if (predicate(a)) Success(a)
else Failure(NonEmptyList.nel(error, IList.empty))
}
def mustNot(predicate: A => Boolean, error: E): Rule =
Reader { liftNel(_)(predicate, error) }
def rules: NonEmptyList[Rule]
implicit val firstSemigroup: Semigroup[A] =
Semigroup.firstSemigroup
def validate(candidate: A): Validated[A] =
rules.sumr1.run(candidate)
}
Validated[A]
import scalaz.ValidationNel
package object semigroupexamples {
type Validated[A] = ValidationNel[ValidationError, A]
}
object Name extends ValidationRules[Name] {
type E = NameError
sealed trait NameError extends ValidationError
case object NameCannotBeEmpty extends NameError
case object NameCannotBeWhitespaceOnly extends NameError
override def rules: NonEmptyList[Rule] =
NonEmptyList(
mustNot(_.value.isWhitespaceOnly,
NameCannotBeWhitespaceOnly),
mustNot(_.value.isEmpty, NameCannotBeEmpty)
)
def apply(s: String): Validated[Name] =
validate(new Name(s) {})
}
sealed abstract case class Name private(value: String)
package semigroupexamples
import scalaz.NonEmptyList
object Age extends ValidationRules[Age] {
type E = AgeError
sealed trait AgeError extends ValidationError
case object AgeNegative extends AgeError
case object AgeTooBig extends AgeError
val MaxAge: Int = Byte.MaxValue.toInt // 127
override def rules: NonEmptyList[Rule] =
NonEmptyList(
mustNot(_.value < 0, AgeNegative),
mustNot(_.value > MaxAge, AgeTooBig)
)
def apply(age: Int): Validated[Age] =
validate(new Age(age) {})
}
sealed abstract case class Age private(value: Int)
package semigroupexamples
import scalaz.syntax.apply._
object Person {
def apply(name: String, age: Int): Validated[Person] =
^(Name(name), Age(age)){ Person.apply }
}
case class Person(name: Name, age: Age)