forked from markhibberd/introduction-to-fp-in-scala
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathEqual.scala
127 lines (104 loc) · 2.91 KB
/
Equal.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package intro
/**
* Type safe equality.
*/
sealed trait Equal[A] {
def equal(a1: A, a2: A): Boolean
}
object Equal {
/**
* Convenience for summoning an Equal instance.
*
* usage: Equal[Int].equal(1, 2)
*/
def apply[A: Equal]: Equal[A] =
implicitly[Equal[A]]
/**
* Convenience for constructing an Equal instance from
* a function.
*/
def from[A](f: (A, A) => Boolean): Equal[A] =
new Equal[A] { def equal(a1: A, a2: A) = f(a1, a2) }
/**
* Convenience for constructing an Equal instance from
* built in scala equals.
*/
def derived[A]: Equal[A] =
from[A](_ == _)
/* Equal Instances */
implicit def StringEqual =
derived[String]
implicit def IntEqual =
derived[Int]
implicit def OptionEqual[A: Equal]: Equal[Option[A]] =
from[Option[A]]({
case (Some(a1), Some(a2)) => Equal[A].equal(a1, a2)
case (None, None) => true
case (None, Some(_)) => false
case (Some(_), None) => false
})
implicit def OptionalEqual[A: Equal]: Equal[Optional[A]] =
from[Optional[A]]({
case (Full(a1), Full(a2)) => Equal[A].equal(a1, a2)
case (Empty(), Empty()) => true
case (Empty(), Full(_)) => false
case (Full(_), Empty()) => false
})
implicit def ListEqual[A: Equal] =
from[List[A]](_.corresponds(_)(Equal[A].equal))
implicit def Tuple2Equal[A: Equal, B: Equal] =
from[(A, B)]({
case ((a1, b1), (a2, b2)) =>
Equal[A].equal(a1, a2) && Equal[B].equal(b1, b2)
})
implicit def Tuple3Equal[A: Equal, B: Equal, C: Equal] =
from[(A, B, C)]({
case ((a1, b1, c1), (a2, b2, c2)) =>
Equal[A].equal(a1, a2) && Equal[B].equal(b1, b2) && Equal[C].equal(c1, c2)
})
implicit def ThrowableEqual =
derived[Throwable]
}
/**
* Syntactic support for the Equal type class.
*
* Anywhere this is in scope the `===` operator
* can be used for type safe equality.
*
* Usage:
* import EqualSyntax._
*
* 1 === 2
*
* 1 === "hello" // doesn't compile
*/
object EqualSyntax {
implicit class AnyEqualSyntax[A: Equal](value: A) {
def ===(other: A) =
Equal[A].equal(value, other)
}
}
/**
* Type classes should have laws, these are the laws for the
* Equal type class.
*/
object EqualLaws {
def commutative[A: Equal](a1: A, a2: A): Boolean =
Equal[A].equal(a1, a2) == Equal[A].equal(a2, a1)
def reflexive[A: Equal](f: A): Boolean =
Equal[A].equal(f, f)
def transitive[A: Equal](a1: A, a2: A, a3: A): Boolean =
!(Equal[A].equal(a1, a2) && Equal[A].equal(a2, a3)) || Equal[A].equal(a1, a3)
}
/**
* Example usage.
*/
object EqualExample {
import EqualSyntax._
1 === 1
// 1 === "hello" -- doesn't compile
def filterLike[A: Equal](a: A, xs: List[A]) =
xs.filter(x => x === a)
filterLike(1, List(1, 2, 3)) // List(1)
// filterLike(1.0d, List(1.0d, 2.0d)) -- doesn't compile because we didn't define an instance for double
}