equality for all!

24
Equality For All! Scala by the Bay, 2014 Bill Venners Artima, Inc. Escalate Software Saturday, August 9, 2014

Upload: bvenners

Post on 26-Dec-2014

112 views

Category:

Engineering


2 download

DESCRIPTION

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

TRANSCRIPT

Page 1: Equality For All!

Equality For All!Scala by the Bay, 2014

Bill VennersArtima, Inc.

Escalate Software

Saturday, August 9, 2014

Page 2: Equality For All!

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

Page 3: Equality For All!

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

Page 4: Equality For All!

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

Page 5: Equality For All!

scala> 42 == 42Lres0: Boolean = true

scala> BigDecimal(42) == '*'res1: Boolean = true

scala> 42.0F == BigInt(42)res2: Boolean = true

scala> new java.lang.Float(42.0F) == 42.toShortres3: Boolean = true

Co-operative Equality Between Types

Saturday, August 9, 2014

Page 6: Equality For All!

scala> import scala.collection.mutableimport 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

Page 7: Equality For All!

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.9999999999res10: Boolean = false

scala> "case" == "CASE"res11: Boolean = false

Wanted: Alternate equalities

Saturday, August 9, 2014

Page 8: Equality For All!

scala> "forty two" == 42res19: 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

Page 9: Equality For All!

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

Page 10: Equality For All!

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.emptyres4: Boolean = true

Fail to compile if L will always equal R.

Saturday, August 9, 2014

Page 11: Equality For All!

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

Page 12: Equality For All!

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

Page 13: Equality For All!

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) === 42res3: 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

Page 14: Equality For All!

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.toIntconvertDigitStringToInt: (d: DigitString)Int

scala> DigitString("42") === DigitString("042")res0: Boolean = false

scala> DigitString("42").toInt === DigitString("042").toIntres1: Boolean = true

John C. Reynolds: Using category theory to design implicit conversions and generic operators.

Saturday, August 9, 2014

Page 15: Equality For All!

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

Page 16: Equality For All!

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

Page 17: Equality For All!

scala> def eqv[T](a: T, b: T): Boolean = a === beqv: [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

Page 18: Equality For All!

EqualityPolicy

UncheckedEquality CheckedEquality EnabledEquality

Saturday, August 9, 2014

Page 19: Equality For All!

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

Page 20: Equality For All!

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

Page 21: Equality For All!

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

Page 22: Equality For All!

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

Page 23: Equality For All!

if sufficientTimeRemains then (Q => A) else thanks

artima.com/shop

15% discountcoupon code:

BYTHEBAY2014

Saturday, August 9, 2014

Page 24: Equality For All!

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

Scalacticscala> 1L === 1res7: Boolean = true

scala> 1 === 1Lres8: Boolean = true

Equal[T], Eq[T]

EqualityConstraint[L, R]

Equivalence[T]

Saturday, August 9, 2014