Skip to content

Commit

Permalink
Improved TypedEquality so it doesn't allocate, and is faster.
Browse files Browse the repository at this point in the history
It is simpler now - =#= requires the arguments to be of the exact same
type.
  • Loading branch information
mbeckerle committed Sep 13, 2016
1 parent c21e500 commit cea3c72
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 52 deletions.
Expand Up @@ -77,19 +77,19 @@ object ElementBase {
* Shared by all forms of elements, local or global or element reference.
*/
abstract class ElementBase(xmlArg: Node, parent: SchemaComponent, position: Int)
extends Term(xmlArg, parent, position)
with AnnotatedMixin
with Element_AnnotationMixin
with NillableMixin
with DFDLStatementMixin
with ElementBaseGrammarMixin
with ElementRuntimeValuedPropertiesMixin
with StringTextMixin
with NumberTextMixin
with CalendarTextMixin
with BooleanTextMixin
with TextNumberFormatMixin
with RealTermMixin {
extends Term(xmlArg, parent, position)
with AnnotatedMixin
with Element_AnnotationMixin
with NillableMixin
with DFDLStatementMixin
with ElementBaseGrammarMixin
with ElementRuntimeValuedPropertiesMixin
with StringTextMixin
with NumberTextMixin
with CalendarTextMixin
with BooleanTextMixin
with TextNumberFormatMixin
with RealTermMixin {

override final def eBase = this

Expand Down Expand Up @@ -670,7 +670,7 @@ abstract class ElementBase(xmlArg: Node, parent: SchemaComponent, position: Int)
if (!isFixedLength) false
else {
val fl = fixedLengthValue
n =#= fl
n.toLong =#= fl
}
}

Expand Down
Expand Up @@ -55,43 +55,58 @@ package object equality {

// Convertible types - strongly typed equality

implicit class ViewEqual[L](val left: L) extends AnyVal {
def =#=[R](right: R)(implicit equality: ViewEquality[L, R]): Boolean =
equality.areEqual(left, right)
def !=#=[R](right: R)(implicit equality: ViewEquality[L, R]): Boolean =
!equality.areEqual(left, right)
}
@implicitNotFound("View equality requires ${L} and ${R} to be in an implicit conversion relationship, i.e. one can be viewed as the other!")
private[equality] sealed trait ViewEquality[L, R] {
def areEqual(left: L, right: R): Boolean
}
private[equality] object ViewEquality extends LowPriorityViewEqualityImplicits {
implicit def rightToLeftEquality[L, R](implicit view: R => L): ViewEquality[L, R] =
new RightToLeftViewEquality(view)
}
private[equality] trait LowPriorityViewEqualityImplicits {
implicit def leftToRightEquality[L, R](implicit view: L => R): ViewEquality[L, R] =
new LeftToRightViewEquality(view)
}
private class LeftToRightViewEquality[L, R](view: L => R) extends ViewEquality[L, R] {
override def areEqual(left: L, right: R): Boolean =
view(left) == right
}
private class RightToLeftViewEquality[L, R](view: R => L) extends ViewEquality[L, R] {
override def areEqual(left: L, right: R): Boolean =
left == view(right)
implicit class ViewEqual[T](val left: T) extends AnyVal {
@inline def =#=(right: T) = left == right
@inline def !=#=(right: T) = left != right
}
// implicit class ViewEqual[L](val left: L) extends AnyVal {
// def =#=[R](right: R)(implicit equality: ViewEquality[L, R]): Boolean =
// equality.areEqual(left, right)
// @inline def !=#=[R](right: R)(implicit equality: ViewEquality[L, R]): Boolean =
// !equality.areEqual(left, right)
// }
// @implicitNotFound("View equality requires ${L} and ${R} to be in an implicit conversion relationship, i.e. one can be viewed as the other!")
// private[equality] sealed trait ViewEquality[L, R] extends Any {
// def areEqual(left: L, right: R): Boolean
// }
//
// private[equality] object ViewEquality extends LowPriorityViewEqualityImplicits {
// implicit def rightToLeftEquality[L, R](implicit view: R => L): ViewEquality[L, R] =
// new RightToLeftViewEquality(view)
// }
// private[equality] trait LowPriorityViewEqualityImplicits extends Any {
// implicit def leftToRightEquality[L, R](implicit view: L => R): ViewEquality[L, R] =
// new LeftToRightViewEquality(view)
// }
//
// /**
// * At least in theory this is a value class and so has no representation.
// *
// * Unfortunately it does seem that if you use =#= then if you examine
// * the byte code there is a call to NEW. But it might be for some closure.
// * Associated with the implicit view converter being passed.
// * It doesn't seem to correspond directly to the calls to new in the ViewEquality
// * object's methods.
// */
// private[equality] class LeftToRightViewEquality[L, R](val view: L => R) extends AnyVal with ViewEquality[L, R] {
// override def areEqual(left: L, right: R): Boolean =
// view(left) == right
// }
// private[equality] class RightToLeftViewEquality[L, R](val view: R => L) extends AnyVal with ViewEquality[L, R] {
// override def areEqual(left: L, right: R): Boolean =
// left == view(right)
// }

// Type wise - allows bi-directional subtypes, not just subtype on right.

implicit class TypeEqual[L <: AnyRef](val left: L) extends AnyVal {
def =:=[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
@inline def =:=[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
equality.areEqual(left, right)
def !=:=[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
@inline def !=:=[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
!equality.areEqual(left, right)
def _eq_[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
@inline def _eq_[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
equality.areEq(left, right)
def _ne_[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
@inline def _ne_[R <: AnyRef](right: R)(implicit equality: TypeEquality[L, R]): Boolean =
!equality.areEq(left, right)
}

Expand All @@ -100,18 +115,23 @@ package object equality {
def areEqual(left: L, right: R): Boolean
def areEq(left: L, right: R): Boolean
}

private[equality] object TypeEquality extends LowPriorityTypeEqualityImplicits {
implicit def rightSubtypeOfLeftEquality[L <: AnyRef, R <: L]: TypeEquality[L, R] =
@inline implicit def rightSubtypeOfLeftEquality[L <: AnyRef, R <: L]: TypeEquality[L, R] =
AnyTypeEquality.asInstanceOf[TypeEquality[L, R]]
}
private[equality] trait LowPriorityTypeEqualityImplicits {
implicit def leftSubtypeOfRightEquality[R <: AnyRef, L <: R]: TypeEquality[L, R] =
@inline implicit def leftSubtypeOfRightEquality[R <: AnyRef, L <: R]: TypeEquality[L, R] =
AnyTypeEquality.asInstanceOf[TypeEquality[L, R]]
}
private object AnyTypeEquality extends TypeEquality[AnyRef, AnyRef] {
override def areEqual(left: AnyRef, right: AnyRef): Boolean =

// must be public or scala compiler complains that it can't embed the
// static reference.
//
object AnyTypeEquality extends TypeEquality[AnyRef, AnyRef] {
@inline override def areEqual(left: AnyRef, right: AnyRef): Boolean =
left == right
override def areEq(left: AnyRef, right: AnyRef): Boolean =
@inline override def areEq(left: AnyRef, right: AnyRef): Boolean =
left eq right
}

Expand Down
Expand Up @@ -30,20 +30,21 @@
* SOFTWARE.
*/

package edu.illinois.ncsa.daffodil.equality
package edu.illinois.ncsa.daffodil.testEquality

import org.junit.Test
import org.junit.Assert._
import edu.illinois.ncsa.daffodil.equality._

class TestEqualityOperators {

@Test
def testConveribleNumberEquality() {
val x = 5
val y = 6L
assertFalse(x =#= y)
assertFalse(x.toLong =#= y)
// assertFalse (x =#= "x") // compile error - wrong types
assertFalse(5 =#= 6.0)
assertFalse(5 =#= 6.0.toInt)
}

@Test
Expand All @@ -54,4 +55,40 @@ class TestEqualityOperators {
// assertFalse(x =:= "List(1, 2, 3") // compile error
assertFalse(Nil =:= x)
}
}

// prevent optimizations from using constant objects
val xObj = if (scala.math.random == -0.0) "foo" else "bar"
val yObj = if (scala.math.random == -0.0) "bar" else "foo"

@Test
def testStronglyTypedEqualityInlineAnyRef() {
if (TestEqualityOperators.compare(xObj, yObj))
fail("equal")
}

private val ylong = scala.math.random.toLong

@Test
def testStronglyTypedEqualityInline() {
val x = 5
val y = ylong
if (TestEqualityOperators.compareIntLong(x, y))
fail("equal")
}
}

/**
* By looking at the byte code for the methods of this
* object, one can determine whether these typed equality operators
* are allocating or not, and what code is generated.
*/
object TestEqualityOperators {

def compare(x: String, y: String) = {
x =:= y
}

def compareIntLong(x: Int, y: Long) = {
x.toLong =#= y
}
}

0 comments on commit cea3c72

Please sign in to comment.