Skip to content

Instantly share code, notes, and snippets.

@ashleymercer
Last active March 25, 2019 15:49
Show Gist options
  • Save ashleymercer/08aeb8f1d6c9e0837c751ce992ffe65a to your computer and use it in GitHub Desktop.
Save ashleymercer/08aeb8f1d6c9e0837c751ce992ffe65a to your computer and use it in GitHub Desktop.
Draft proposal for encoding scala validations, based on https://github.com/davegurnell/checklist
object checklist {
import cats.Applicative
import cats.Monoid
import cats.Semigroup
import checklist.Checked._
import scala.language.implicitConversions
sealed trait Checked[+E, +W, +A] {
def map[B](f: A => B): Checked[E, W, B] = this match {
case Pass(w, a) => Pass(w, f(a))
case f @ Fail(_, _) => f
}
// Similar to cats.data.Validated, this is functionally equivalent
// to flatMap but is explicitly *not* called flatMap because Checked
// does not have monadic (fail-fast) semantics
def andThen[EE >: E, WW >: W, B](f: A => Checked[EE, WW, B])(implicit WW: Semigroup[WW]): Checked[EE, WW, B] = this match {
case Pass(wa, a) => f(a) match {
case Pass(wf, b) => Pass(WW.combine(wa, wf), b)
case Fail(e, wf) => Fail(e, WW.combine(wa, wf))
}
case f @ Fail(_, _) => f
}
def ap[EE >: E, WW >: W, B](ff: Checked[EE, WW, A => B])(implicit EE: Semigroup[EE], WW: Semigroup[WW]): Checked[EE, WW, B] = (this, ff) match {
case (Pass(wa, a), Pass(wf, f)) => Pass(WW.combine(wa, wf), f(a))
case (Fail(ea, wa), Pass(wf, _)) => Fail(ea, WW.combine(wa, wf))
case (Pass(wa, _), Fail(ef, wf)) => Fail(ef, WW.combine(wa, wf))
case (Fail(ea, wa), Fail(ef, wf)) => Fail(EE.combine(ea, ef), WW.combine(wa, wf))
}
}
object Checked extends CheckedInstances {
case class Pass[+W, +A](warnings: W, value: A) extends Checked[Nothing, W, A]
case class Fail[+E, +W](errors: E, warnings: W) extends Checked[E, W, Nothing]
}
private[checklist] class CheckedOps[A](val a: A) extends AnyVal {
def pass[W](implicit W: Monoid[W]): Checked[Nothing, W, A] = new Pass[W, A](W.empty, a)
def fail[W](implicit W: Monoid[W]): Checked[A, W, Nothing] = new Fail[A, W](a, W.empty)
def warn[W](w: W): Checked[Nothing, W, A] = new Pass[W, A](w, a)
}
private[checklist] class CheckedApplicative[E, W](implicit E: Semigroup[E], W: Monoid[W]) extends Applicative[Checked[E, W, ?]] {
override def pure[A](a: A): Checked[E, W, A] = Pass(W.empty, a)
override def ap[A, B](ff: Checked[E, W, A => B])(fa: Checked[E, W, A]): Checked[E, W, B] = fa.ap(ff)
override def map[A, B](fa: Checked[E, W, A])(f: A => B): Checked[E, W, B] = fa.map(f)
}
trait CheckedSyntax {
implicit final def checkedSyntax[A](a: A): CheckedOps[A] = new CheckedOps[A](a)
}
trait CheckedInstances {
implicit final def catsDataApplicativeForChecked[E, W](implicit E: Semigroup[E], W: Monoid[W]): Applicative[Checked[E, W, ?]] = new CheckedApplicative[E, W]
}
object syntax extends CheckedSyntax
}
object ChecklistApp extends App {
import cats.data.NonEmptyList
import cats.instances.list._
import cats.syntax.traverse._
import checklist.syntax._
import scala.io.Source
case class User(id: Long, username: String, email: String, age: Int)
val csv: String =
""" 1,Phil Collins,phil.collins@example.com,35
| -1,Isaac Newton,,400
| 5,John Doe,john.doe@example,125
| 6,Alex Jones,alex@example.com,26
""".stripMargin
val res = Source.fromString(csv)
.getLines()
.map { _.split(",").map(_.trim) }
.map {
case Array(_ , _ , _ , age) if age.toInt > 150 => NonEmptyList.one("Age is impossible").fail
case Array(id, username, email, age) if age.toInt > 100 => User(id.toLong, username, email, age.toInt).warn(List("Age is suspicious"))
case Array(id, username, email, age) => User(id.toLong, username, email, age.toInt).pass
case arr => NonEmptyList.one(s"Expected CSV line to be 4 elements long, but was ${arr.length}").fail
}
.toList
System.out.println("Raw elements:")
System.out.println("-------------")
res.foreach(System.out.println)
System.out.println()
System.out.println("Sequenced:")
System.out.println("----------")
System.out.println(res.sequence)
System.out.println()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment