forked from charleso/introduction-to-fp-in-scala
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathEqual.scala
130 lines (106 loc) · 3 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
128
129
130
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: Equal[String] =
derived[String]
implicit def CharEqual: Equal[Char] =
derived[Char]
implicit def IntEqual: Equal[Int] =
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
}