Skip to content

Commit e134e98

Browse files
authored
Merge pull request #12 from scala/universal-equals
Add universal equality
2 parents e184a24 + 0d4be9d commit e134e98

File tree

5 files changed

+134
-4
lines changed

5 files changed

+134
-4
lines changed

src/main/scala/strawman/collection/Iterable.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import scala.reflect.ClassTag
66
import scala.{Int, Boolean, Array, Any, Unit, StringContext}
77
import java.lang.{String, UnsupportedOperationException}
88
import strawman.collection.mutable.{ArrayBuffer, StringBuilder}
9+
import java.lang.String
910

1011
/** Base trait for generic collections */
1112
trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] {
@@ -53,7 +54,7 @@ trait IterableFactory[+C[X] <: Iterable[X]] extends FromIterable[C] {
5354
*/
5455
trait IterableOps[+A] extends Any {
5556
protected def coll: Iterable[A]
56-
private def iterator() = coll.iterator()
57+
private def iterator(): Iterator[A] = coll.iterator()
5758

5859
/** Apply `f` to each element for its side effects
5960
* Note: [U] parameter needed to help scalac's type inference.
@@ -138,8 +139,10 @@ trait IterableOps[+A] extends Any {
138139
}
139140

140141
override def toString = s"$className(${mkString(", ")})"
142+
141143
}
142144

145+
143146
/** Type-preserving transforms over iterables.
144147
* Operations defined here return in their result iterables of the same type
145148
* as the one they are invoked on.

src/main/scala/strawman/collection/Iterator.scala

+9
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ trait Iterator[+A] extends IterableOnce[A] { self =>
9797
def hasNext = self.hasNext && thatIterator.hasNext
9898
def next() = (self.next(), thatIterator.next())
9999
}
100+
def sameElements[B >: A](that: IterableOnce[B]): Boolean = {
101+
val those = that.iterator()
102+
while (hasNext && those.hasNext)
103+
if (next() != those.next())
104+
return false
105+
// At that point we know that *at least one* iterator has no next element
106+
// If *both* of them have no elements then the collections are the same
107+
hasNext == those.hasNext
108+
}
100109
}
101110

102111
object Iterator {

src/main/scala/strawman/collection/Seq.scala

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package strawman.collection
22

3-
import scala.{Any, Boolean, Int, IndexOutOfBoundsException}
3+
import scala.{Any, Boolean, Equals, IndexOutOfBoundsException, Int}
44
import strawman.collection.immutable.{List, Nil}
55

66
import scala.annotation.unchecked.uncheckedVariance
7+
import scala.util.hashing.MurmurHash3
78

89
/** Base trait for sequence collections */
910
trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] with ArrayLike[A]
@@ -51,6 +52,50 @@ trait IndexedSeq[+A] extends Seq[A] { self =>
5152
trait SeqLike[+A, +C[X] <: Seq[X]]
5253
extends IterableLike[A, C]
5354
with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote
55+
with Equals {
56+
57+
protected def coll: C[A @uncheckedVariance]
58+
59+
/** Do the elements of this collection are the same (and in the same order)
60+
* as those of `that`?
61+
*/
62+
def sameElements[B >: A](that: IterableOnce[B]): Boolean =
63+
coll.iterator().sameElements(that)
64+
65+
/** Method called from equality methods, so that user-defined subclasses can
66+
* refuse to be equal to other collections of the same kind.
67+
* @param that The object with which this $coll should be compared
68+
* @return `true`, if this $coll can possibly equal `that`, `false` otherwise. The test
69+
* takes into consideration only the run-time types of objects but ignores their elements.
70+
*/
71+
def canEqual(that: Any): Boolean = true
72+
73+
override def equals(o: scala.Any): Boolean =
74+
o match {
75+
case it: Seq[A] => (it canEqual this) && sameElements(it)
76+
case _ => false
77+
}
78+
79+
override def hashCode(): Int =
80+
Seq.stableIterableHash(coll)
81+
82+
}
83+
84+
// Temporary: TODO move to MurmurHash3.scala
85+
object Seq {
86+
87+
final def stableIterableHash(xs: Seq[_]): Int = {
88+
var n = 0
89+
var h = "Seq".##
90+
val it = xs.iterator()
91+
while (it.hasNext) {
92+
h = MurmurHash3.mix(h, it.next().##)
93+
n += 1
94+
}
95+
MurmurHash3.finalizeHash(h, n)
96+
}
97+
98+
}
5499

55100
/** Base trait for linear Seq operations */
56101
trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] {
+60-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,63 @@
11
package strawman
22
package collection
33

4-
trait Set[A] extends Iterable[A] {}
4+
import scala.{Any, Boolean, Equals, Int}
5+
import scala.util.hashing.MurmurHash3
6+
7+
trait Set[A]
8+
extends Iterable[A]
9+
with SetLike[A, Set]
10+
11+
trait SetLike[A, +C[X] <: Set[X]]
12+
extends SetOps[A]
13+
with Equals {
14+
15+
protected def coll: C[A]
16+
17+
def canEqual(that: Any) = true
18+
19+
override def equals(that: Any): Boolean =
20+
that match {
21+
case set: Set[A] =>
22+
(this eq set) ||
23+
(set canEqual this) &&
24+
(coll.size == set.size) &&
25+
(this subsetOf set)
26+
case _ => false
27+
}
28+
29+
override def hashCode(): Int = Set.setHash(coll)
30+
31+
}
32+
33+
trait SetOps[A] extends Any {
34+
35+
protected def coll: Set[A]
36+
37+
def subsetOf(that: Set[A]): Boolean
38+
39+
}
40+
41+
// Temporary, TODO move to MurmurHash3
42+
object Set {
43+
44+
def setHash(xs: Set[_]): Int = unorderedHash(xs, "Set".##)
45+
46+
final def unorderedHash(xs: Iterable[_], seed: Int): Int = {
47+
var a, b, n = 0
48+
var c = 1
49+
xs foreach { x =>
50+
val h = x.##
51+
a += h
52+
b ^= h
53+
if (h != 0) c *= h
54+
n += 1
55+
}
56+
var h = seed
57+
h = MurmurHash3.mix(h, a)
58+
h = MurmurHash3.mix(h, b)
59+
h = MurmurHash3.mixLast(h, c)
60+
MurmurHash3.finalizeHash(h, n)
61+
}
62+
63+
}

src/test/scala/strawman/collection/test/Test.scala

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package strawman.collection.test
22

33
import java.lang.String
44
import scala.{Int, Unit, Array, StringContext, Boolean, Any, Char}
5-
import scala.Predef.{println, charWrapper}
5+
import scala.Predef.{assert, println, charWrapper}
66

77
import strawman.collection.immutable._
88
import strawman.collection.mutable._
@@ -288,6 +288,19 @@ class StrawmanTest {
288288
println(xs17.to(List))
289289
}
290290

291+
def equality(): Unit = {
292+
val list = List(1, 2, 3)
293+
val lazyList = LazyList(1, 2, 3)
294+
val buffer = ArrayBuffer(1, 2, 3)
295+
assert(list == lazyList)
296+
assert(list.## == lazyList.##)
297+
assert(list == buffer)
298+
assert(list.## == buffer.##)
299+
buffer += 4
300+
assert(list != buffer)
301+
assert(list.## != buffer.##)
302+
}
303+
291304
@Test
292305
def mainTest: Unit = {
293306
val ints = List(1, 2, 3)
@@ -301,5 +314,6 @@ class StrawmanTest {
301314
stringOps("abc")
302315
arrayOps(Array(1, 2, 3))
303316
lazyListOps(LazyList(1, 2, 3))
317+
equality()
304318
}
305319
}

0 commit comments

Comments
 (0)