Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Equality For All!

Bill Venners lightening talk about recent work on equality in Scalactic and ScalaTest from the Scala by the Bay 2014 conference.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all
  • Be the first to comment

  • Be the first to like this

Equality For All!

  1. 1. Equality For All! Scala by the Bay, 2014 BillVenners Artima, Inc. Escalate Software Saturday, August 9, 2014
  2. 2. Equality: An equivalence relation with one element per equivalence class 42 4341 42 = 4241 = 41 43 = 43 reflexive: x = x symmetric: x = y iff y = x transitive: if x = y and y = z then x = z Saturday, August 9, 2014
  3. 3. How do I say forty two in code? Let me count the ways... 42 42L 42.0 42.0F 42.toShort '*' 42.toByte BigInt(42) BigDecimal(42) new java.lang.Integer(42) new java.lang.Long(42L) new java.lang.Double(42.0) new java.lang.Float(42.0F) new java.lang.Short(42.toShort) new java.lang.Character(42) new java.lang.Byte(42.toByte) new java.math.BigInteger("42") new java.math.BigDecimal(42) Complex(42.0, 0.0) DigitString("42") DigitString("042") Saturday, August 9, 2014
  4. 4. The equals method implements an equivalence relation on non-null object references: • It is reflexive: for any non-null reference value x, x.equals(x) should return true. • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true. • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true. • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified. • For any non-null reference value x, x.equals(null) should return false. Java's equals contract public boolean equals(Object obj) final def == (other:Any): Boolean Saturday, August 9, 2014
  5. 5. scala> 42 == 42L res0: Boolean = true scala> BigDecimal(42) == '*' res1: Boolean = true scala> 42.0F == BigInt(42) res2: Boolean = true scala> new java.lang.Float(42.0F) == 42.toShort res3: Boolean = true Co-operative Equality Between Types Saturday, August 9, 2014
  6. 6. scala> import scala.collection.mutable import scala.collection.mutable scala> Set(BigInt(4), BigInt(2)) == mutable.Set(4.toByte, 2.toByte) res4: Boolean = true scala> Vector(Left(4), Right(2)) == List(Left(4L), Right(2L)) res5: Boolean = true scala> List(mutable.Set(Map(Some(4L) -> BigInt(2)))) == Vector(Set(mutable.Map(Some(4.0) -> new java.lang.Long(2L)))) res6: Boolean = true Co-operative Equality Between Types Saturday, August 9, 2014
  7. 7. scala> Array(4, 2) == Array(4, 2) res7: Boolean = false scala> <forty><two></two></forty> == <forty> <two></two> </forty> res8: Boolean = false scala> new java.math.BigDecimal("42.0") == new java.math.BigDecimal("42.00") res9: Boolean = false scala> 42.0 == 41.9999999999 res10: Boolean = false scala> "case" == "CASE" res11: Boolean = false Wanted: Alternate equalities Saturday, August 9, 2014
  8. 8. scala> "forty two" == 42 res19: Boolean = false scala> List(mutable.Set(Map(Some(4L) -> BigInt(2)))) == Vector(Set(mutable.Map(Some("4.0") -> new java.lang.Long(2L)))) res23: Boolean = false Wanted: Type errors But how to decide which comparisons compile? Saturday, August 9, 2014
  9. 9. scala> "forty two" == 42 <console>:20: error: types String and Int do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.EqualityConstraint[String,Int] "forty two" === 42 ^ scala> BigInt(42) === BigDecimal(42) res1: Boolean = true Fail to compile if L can never equal R. Saturday, August 9, 2014
  10. 10. scala> Vector.empty === List.empty <console>:20: error: ambiguous implicit values: both method conflictingEmptySeqConstraint1 in object EqualityConstraint of type [LSEQ[e] <: scala.collection.GenSeq[e], RSEQ[e] <: scala.collection.GenSeq[e]]=> org.scalactic.EqualityConstraint[LSEQ[Nothing],RSEQ[Nothing]] and method conflictingEmptySeqConstraint2 in object EqualityConstraint of type [LSEQ[e] <: scala.collection.GenSeq[e], RSEQ[e] <: scala.collection.GenSeq[e]]=> org.scalactic.EqualityConstraint[LSEQ[Nothing],RSEQ[Nothing]] match expected type org.scalactic.EqualityConstraint[scala.collection.immutable.Vector[A],List[Nothing]] Vector.empty === List.empty ^ scala> Vector.empty === List.empty[Int] res3: Boolean = true scala> Vector.empty[String] === List.empty res4: Boolean = true Fail to compile if L will always equal R. Saturday, August 9, 2014
  11. 11. Candidate rule: To compile, an equality comparison must be interesting: values of types L and R can be either equal or unequal. Saturday, August 9, 2014
  12. 12. scala> case class Complex(real: Double, imaginary: Double) defined class Complex scala> implicit def convertIntToComplex(i: Int): Complex = Complex(i, 0.0) convertIntToComplex: (i: Int)Complex scala> 42 === Complex(42, 0) <console>:24: error: types Int and Complex do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.EqualityConstraint[Int,Complex] 42 === Complex(42, 0) ^ scala> Complex(42, 0) === 42 <console>:24: error: types Complex and Int do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.EqualityConstraint[Complex,Int] Complex(42, 0) === 42 ^ What about implicit conversions? Saturday, August 9, 2014
  13. 13. scala> implicit val enabler = EqualityEnabledBetween[Int, Complex] enabler: org.scalactic.EqualityEnabledBetween[Int,Complex] = org.scalactic.EqualityEnabledBetween@e5d2d9b scala> 42 === Complex(42, 0) res2: Boolean = true scala> Complex(42, 0) === 42 res3: Boolean = true scala> new AnyShouldWrapper(1) === 1 // Probably shouldn't enable... Intuition: enable some but not all But what would the rule be? Saturday, August 9, 2014
  14. 14. OK if the conversion is an injection scala> case class DigitString(digits: String) { | val toInt: Int = digits.toInt | } defined class DigitString scala> implicit def convert(d: DigitString): Int = | d.digits.toInt convertDigitStringToInt: (d: DigitString)Int scala> DigitString("42") === DigitString("042") res0: Boolean = false scala> DigitString("42").toInt === DigitString("042").toInt res1: Boolean = true John C. Reynolds: Using category theory to design implicit conversions and generic operators. Saturday, August 9, 2014
  15. 15. How to decide: 1.To compile, an equality comparison must be interesting: values of types L and R can be either equal or unequal. 2.Allow select implicit conversions to be enabled, and recommend that non-widening conversions (non-injections) not be enabled. Saturday, August 9, 2014
  16. 16. scala> (Some(1): Option[Int]) === Some(1) res0: Boolean = true scala> Some(1) === (Some(1): Option[Int]) res1: Boolean = true scala> Some(1) === Some(1) res2: Boolean = true What about the implicit conversion from subtype to supertype (<:<)? Saturday, August 9, 2014
  17. 17. scala> def eqv[T](a: T, b: T): Boolean = a === b eqv: [T](a: T, b: T)Boolean scala> eqv(1, ()) res3: Boolean = false scala> ((i: Int) => i + 1) === ((i: Int) => i + 1) res4: Boolean = false Even though <:< is an injection, is it always desireable? Saturday, August 9, 2014
  18. 18. EqualityPolicy UncheckedEquality CheckedEquality EnabledEquality Saturday, August 9, 2014
  19. 19. scala> import EnabledEquality._ import EnabledEquality._ scala> def eqv[T](a: T, b: T): Boolean = a === b <console>:19: error: types T and T do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.EqualityConstraint[T,T] def eqv[T](a: T, b: T): Boolean = a === b ^ scala> ((i: Int) => i + 1) === ((i: Int) => i + 1) <console>:20: error: types Int => Int and Int => Int do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.EqualityConstraint[Int => Int,Int => Int] ((i: Int) => i + 1) === ((i: Int) => i + 1) ^ EnabledEquality benefit Saturday, August 9, 2014
  20. 20. scala> case class Person(name: String) defined class Person scala> Person("Sue") === Person("Sue") <console>:22: error: types Person and Person do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.EqualityConstraint[Person,Person] Person("Sue") === Person("Sue") ^ scala> implicit val enabler = new EqualityEnabledFor[Person] enabler: org.scalactic.EqualityEnabledFor[Person] = org.scalactic.EqualityEnabledFor@1289d391 scala> Person("Sue") === Person("Sue") res2: Boolean = true EnabledEquality cost Saturday, August 9, 2014
  21. 21. scala> 1 should === ("one") <console>:23: error: types Int and String do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.Constraint[Int,String] 1 should === ("one") ^ scala> 1 should equal ("one") <console>:23: error: could not find implicit value for parameter typeClass1: org.scalactic.enablers.EvidenceThat[String]#CanEqual[Int] 1 should equal ("one") ^ scala> 1 should be ("one") <console>:23: error: could not find implicit value for parameter typeClass1: org.scalactic.enablers.EvidenceThat[String]#CanEqual[Int] 1 should be ("one") ^ scala> 1 should be_== ("one") org.scalatest.exceptions.TestFailedException: 1 was not equal to "one" ScalaTest's equal, be, and be_== Saturday, August 9, 2014
  22. 22. scala> List(1, 2, 3) should contain ("one") <console>:23: error: could not find implicit value for parameter typeClass1: org.scalactic.enablers.EvidenceThat[String]#CanBeContainedIn[List[Int]] List(1, 2, 3) should contain ("one") ^ scala> List(1, 2, 3) should contain oneOf ("one", "two") <console>:23: error: could not find implicit value for parameter evidence: org.scalactic.enablers.EvidenceThat[String]#CanBeContainedIn[List[Int]] List(1, 2, 3) should contain oneOf("one", "two") ^ scala> 1 isIn List(1, 2, 3) res14: Boolean = true scala> "one" isIn List(1, 2, 3) <console>:23: error: Could not find evidence that String can be contained in List[Int]; the missing implicit parameter is of type org.scalactic.enablers.ContainingConstraint[List[Int],String] "one" isIn List(1, 2, 3) ^ ScalaTest's contain, Scalactic's isIn/isNotIn Saturday, August 9, 2014
  23. 23. if sufficientTimeRemains then (Q => A) else thanks artima.com/shop 15% discount coupon code: BYTHEBAY2014 Saturday, August 9, 2014
  24. 24. scala> 1L === 1 res0: Boolean = true scala> 1 === 1L <console>:14: error: could not find implicit value for parameter F0: scalaz.Equal[Any] 1 === 1L ^ Scalaz or Spire Scalactic scala> 1L === 1 res7: Boolean = true scala> 1 === 1L res8: Boolean = true Equal[T], Eq[T] EqualityConstraint[L, R] Equivalence[T] Saturday, August 9, 2014

    Be the first to comment

    Login to see the comments

Bill Venners lightening talk about recent work on equality in Scalactic and ScalaTest from the Scala by the Bay 2014 conference.

Views

Total views

780

On Slideshare

0

From embeds

0

Number of embeds

9

Actions

Downloads

5

Shares

0

Comments

0

Likes

0

×