Skip to content

Instantly share code, notes, and snippets.

@kryptt
Last active June 25, 2020 19:19
Show Gist options
  • Save kryptt/db57189b411323beaeaf76b9d07a0468 to your computer and use it in GitHub Desktop.
Save kryptt/db57189b411323beaeaf76b9d07a0468 to your computer and use it in GitHub Desktop.
power of higher kinded types...
import cats.{Bifoldable, Eq, Foldable, Show}
import cats.data.Ior
import cats.instances.either._
import cats.instances.option._
import cats.instances.list._
import cats.syntax.bifoldable._
import cats.syntax.foldable._
import cats.syntax.align._
object DiffExample {
trait Diffable[T] extends Eq[T] with Show[T] {
def diff(value: T, expected: T): Option[String] =
if (eqv(value, expected)) None
else Some(s"was: ${show(value)}; expected: ${show(expected)}")
}
trait LowerPriorityDiffable {
implicit def defaultDiffable[T](implicit EqT: Eq[T], ShowT: Show[T]): Diffable[T] = new Diffable[T] {
override def eqv(x: T, y: T): Boolean = EqT.eqv(x, y)
override def show(t: T): String = ShowT.show(t)
}
protected def listEqv[T](xs: List[T], ys: List[T])(implicit EqT: Eq[T]): Boolean =
xs.alignWith(ys)(eqvAlign(_)(EqT)).forall(identity)
protected def eqvAlign[T](x: Ior[T, T])(implicit EqT: Eq[T]): Boolean = x match {
case Ior.Both(a, b) => EqT.eqv(a, b)
case _ => false
}
}
object Diffable extends LowerPriorityDiffable {
def apply[T](implicit diffable: Diffable[T]): Diffable[T] = diffable
implicit def bifoldableDiffable[F[_, _]: Bifoldable, T: Eq, V: Eq](
implicit ShowF: Show[F[T, V]]): Diffable[F[T, V]] =
new Diffable[F[T, V]] {
override def eqv(x: F[T, V], y: F[T, V]): Boolean =
listEqv(toList(x), toList(y))(catsStdEqForEither(Eq[T], Eq[V]))
override def show(ftv: F[T, V]): String = ShowF.show(ftv)
private def toList(x: F[T, V]): List[Either[T, V]] =
x.bifoldLeft(List.empty[Either[T, V]])((l, t) => Left(t) :: l, (l, v) => Right(v) :: l)
}
}
def expectNone[T: Diffable](received: Option[T]): String =
expect(received, None, note => s"Failed to match None.\n$note")
def expectSome[T: Diffable](received: Option[T], expected: T): String =
expect(received, Some(expected), note => s"Failed to match Some(${Diffable[T].show(expected)}).\n$note")
def expectLeft[T: Diffable, V: Diffable](received: Either[T, V], expected: T): String =
expect(received, Left(expected), note => s"Failed to match Left(${Diffable[T].show(expected)}.\b$note")
def expectRight[T: Diffable, V: Diffable](received: Either[T, V], expected: V): String =
expect(received, Right(expected), note => s"Failed to match Right(${Diffable[V].show(expected)}.\b$note")
private def expect[T: Diffable](received: T, expected: T, msgBuilder: String => String): String =
Diffable[T]
.diff(received, expected)
.fold("Values matched")(msgBuilder)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment