Skip to content

Instantly share code, notes, and snippets.

@tonymorris
Last active December 10, 2015 19:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tonymorris/4481469 to your computer and use it in GitHub Desktop.
Save tonymorris/4481469 to your computer and use it in GitHub Desktop.
Equal library for Scala (standalone)
trait Equal[A] {
def equal(a1: A, a2: A): Boolean
def apply(a1: A, a2: A): Boolean =
equal(a1, a2)
def contramap[B](f: B => A): Equal[B] =
Equal((b1, b2) => apply(f(b1), f(b2)))
def &&[B](b: => Equal[B]): Equal[(A, B)] =
Equal((ab1, ab2) => apply(ab1._1, ab2._1) && b(ab1._2, ab2._2))
def ||[B](b: => Equal[B]): Equal[(A, B)] =
Equal((ab1, ab2) => apply(ab1._1, ab2._1) || b(ab1._2, ab2._2))
def ==>[B](b: => Equal[B]): Equal[(A, B)] =
Equal((ab1, ab2) => !apply(ab1._1, ab2._1) || b(ab1._2, ab2._2))
def <==[B](b: => Equal[B]): Equal[(A, B)] =
Equal((ab1, ab2) => apply(ab1._1, ab2._1) || !b(ab1._2, ab2._2))
def ===[B](b: Equal[B]): Equal[(A, B)] =
Equal((ab1, ab2) => apply(ab1._1, ab2._1) == b(ab1._2, ab2._2))
def =/=[B](b: Equal[B]): Equal[(A, B)] =
negate === b
def negate: Equal[A] =
Equal((a1, a2) => !apply(a1, a2))
def unary_! : Equal[A] =
negate
def zip[B](b: => Equal[B]): Equal[(A, B)] =
&&(b)
def notEqual(a1: A, a2: A): Boolean =
negate(a1, a2)
def curry: A => A => Boolean =
a1 => a2 => apply(a1, a2)
def curryFlip: A => A => Boolean =
a2 => a1 => apply(a1, a2)
def run: (A, A) => Boolean =
(a1, a2) => apply(a1, a2)
def when1(a1: A, a2: A): Option[A] =
if(apply(a1, a2))
Some(a1)
else
None
def when2(a1: A, a2: A): Option[A] =
when1(a2, a1)
def unless1(a1: A, a2: A): Option[A] =
negate.when1(a1, a2)
def unless2(a1: A, a2: A): Option[A] =
negate.when2(a1, a2)
def pick1(a1: A, a2: A): A =
if(apply(a1, a2))
a1
else
a2
def pick2(a1: A, a2: A): A =
negate.pick1(a1, a2)
}
object Equal extends EqualFunctions with EqualInstances {
def apply[A](f: (A, A) => Boolean): Equal[A] =
new Equal[A] {
def equal(a1: A, a2: A) =
f(a1, a2)
}
def always[A](p: => Boolean): Equal[A] =
apply((_, _) => p)
def for1[A](f: A => Boolean): Equal[A] =
apply((a1, _) => f(a1))
def for2[A](f: A => Boolean): Equal[A] =
apply((_, a2) => f(a2))
def curried[A](f: A => A => Boolean): Equal[A] =
apply((a1, a2) => f(a1)(a2))
def counzip[A, B](e: Either[Equal[A], Equal[B]]): Equal[Either[A, B]] =
e match {
case Left(ea) => Equal((a1, a2) => a1.left exists (aa1 => a2.left exists (aa2 => ea(aa1, aa2))))
case Right(eb) => Equal((b1, b2) => b1.right exists (bb1 => b2.right exists (bb2 => eb(bb1, bb2))))
}
}
trait EqualFunctions {
implicit def ToEqualed[A](a: A): Equaled[A] =
Equaled(a)
case class EqualingBy[A](a1: A, a2: A) {
def by[B](f: A => B)(implicit E: Equal[B]): Boolean =
E contramap f apply (a1, a2)
}
case class NotEqualingBy[A](a1: A, a2: A) {
def by[B](f: A => B)(implicit E: Equal[B]): Boolean =
E contramap f notEqual (a1, a2)
}
case class Equaled[A](a: A) {
def ===(a2: A)(implicit E: Equal[A]): Boolean =
E(a, a2)
def =/=(a2: A)(implicit E: Equal[A]): Boolean =
E.notEqual(a, a2)
def >===(a2: A): EqualingBy[A] =
EqualingBy(a, a2)
def >=/=(a2: A): NotEqualingBy[A] =
NotEqualingBy(a, a2)
def equal1(implicit E: Equal[A]): A => Boolean =
E.curry(a)
def equal2(implicit E: Equal[A]): A => Boolean =
E.curryFlip(a)
def when1(a2: A)(implicit E: Equal[A]): Option[A] =
E.when1(a, a2)
def when2(a2: A)(implicit E: Equal[A]): Option[A] =
E.when2(a, a2)
def unless1(a2: A)(implicit E: Equal[A]): Option[A] =
E.unless1(a, a2)
def unless2(a2: A)(implicit E: Equal[A]): Option[A] =
E.when1(a, a2)
def pick1(a2: A)(implicit E: Equal[A]): A =
E.pick1(a, a2)
def pick2(a2: A)(implicit E: Equal[A]): A =
E.pick2(a, a2)
}
}
trait EqualInstances {
// CAUTION
def objectEquals[A]: Equal[A] = new Equal[A] {
def equal(a1: A, a2: A): Boolean = a1 == a2
}
// Scala primitives
implicit val BooleanEqual: Equal[Boolean] = objectEquals
implicit val ByteEqual: Equal[Byte] = objectEquals
implicit val CharEqual: Equal[Char] = objectEquals
implicit val DoubleEqual: Equal[Double] = objectEquals
implicit val FloatEqual: Equal[Float] = objectEquals
implicit val IntEqual: Equal[Int] = objectEquals
implicit val LongEqual: Equal[Long] = objectEquals
implicit val NothingEqual: Equal[Nothing] = Equal.always(true)
implicit val ShortEqual: Equal[Short] = objectEquals
implicit val UnitEqual: Equal[Unit] = Equal.always(true)
// Java primitives
implicit val JavaBooleanEqual: Equal[java.lang.Boolean] = objectEquals
implicit val JavaByteEqual: Equal[java.lang.Byte] = objectEquals
implicit val JavaCharEqual: Equal[java.lang.Character] = objectEquals
implicit val JavaDoubleEqual: Equal[java.lang.Double] = objectEquals
implicit val JavaFloatEqual: Equal[java.lang.Float] = objectEquals
implicit val JavaIntEqual: Equal[java.lang.Integer] = objectEquals
implicit val JavaLongEqual: Equal[java.lang.Long] = objectEquals
implicit val JavaShortEqual: Equal[java.lang.Short] = objectEquals
// big numbers
implicit val BigDecimalEqual: Equal[BigDecimal] = objectEquals
implicit val BigIntegerEqual: Equal[BigInt] = objectEquals
// products
implicit def Function0Equal[A: Equal]: Equal[Function0[A]] =
implicitly[Equal[A]].contramap(_())
implicit def Tuple2Equal[A: Equal, B: Equal]: Equal[(A, B)] =
implicitly[Equal[A]] && implicitly[Equal[B]]
implicit def Tuple3Equal[A: Equal, B: Equal, C: Equal]: Equal[(A, B, C)] =
implicitly[Equal[A]] && implicitly[Equal[B]] && implicitly[Equal[C]] contramap {
case (a, b, c) => ((a, b), c)
}
implicit def Tuple4Equal[A: Equal, B: Equal, C: Equal, D: Equal]: Equal[(A, B, C, D)] =
implicitly[Equal[A]] && implicitly[Equal[B]] && implicitly[Equal[C]] && implicitly[Equal[D]] contramap {
case (a, b, c, d) => (((a, b), c), d)
}
implicit def Tuple5Equal[A: Equal, B: Equal, C: Equal, D: Equal, E: Equal]: Equal[(A, B, C, D, E)] =
implicitly[Equal[A]] && implicitly[Equal[B]] && implicitly[Equal[C]] && implicitly[Equal[D]] && implicitly[Equal[E]] contramap {
case (a, b, c, d, e) => ((((a, b), c), d), e)
}
// sums
implicit def OptionEqual[A: Equal]: Equal[Option[A]] =
Equal((x1, x2) => x1 match {
case None => x2.isEmpty
case Some(a1) => x2 exists (implicitly[Equal[A]].curry(a1))
})
implicit def EitherEqual[A: Equal, B: Equal]: Equal[Either[A, B]] =
Equal((x1, x2) =>
x1 match {
case Left(a1) => x2.left exists (implicitly[Equal[A]].curry(a1))
case Right(b1) => x2.right exists (implicitly[Equal[B]].curry(b1))
})
implicit def ListEqual[A: Equal]: Equal[List[A]] =
Equal((x1, x2) => (x1 corresponds x2)(implicitly[Equal[A]].run))
implicit def StreamEqual[A: Equal]: Equal[Stream[A]] =
Equal((x1, x2) => (x1 corresponds x2)(implicitly[Equal[A]].run))
// collections
implicit def QueueEqual[A: Equal]: Equal[collection.immutable.Queue[A]] =
Equal((x1, x2) => (x1 corresponds x2)(implicitly[Equal[A]].run))
implicit def StackEqual[A: Equal]: Equal[collection.immutable.Stack[A]] =
Equal((x1, x2) => (x1 corresponds x2)(implicitly[Equal[A]].run))
// strings
implicit val StringEqual: Equal[String] = objectEquals
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment