-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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