forked from cs-au-dk/TIP
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GenericLattices.scala
210 lines (161 loc) · 6.03 KB
/
GenericLattices.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package tip.lattices
import scala.language.implicitConversions
/**
* A (semi-)lattice.
*/
trait Lattice {
/**
* The type of the elements of this lattice.
*
* To novice Scala programmers:
* This is an example of an abstract type member. In this trait, `Element` is just a name for a type.
* It is constrained in sub-traits and sub-classes, similarly to type parameters in generic classes.
* For more information about abstract type members in Scala, see [[https://docs.scala-lang.org/tour/abstract-types.html]].
*/
type Element
/**
* The characteristic function of the set of lattice elements.
* Default implementation: returns true for all elements of the right type.
*/
def ch(e: Element) = true
/**
* The bottom element of this lattice.
*/
val bottom: Element
/**
* The top element of this lattice.
* Default: not implemented.
*/
def top: Element = ???
/**
* The least upper bound of `x` and `y`.
*/
def lub(x: Element, y: Element): Element
/**
* Returns true whenever `x` <= `y`.
*/
def leq(x: Element, y: Element): Boolean = lub(x, y) == y // rarely used, but easy to implement :-)
}
/**
* The `n`-th product lattice made of `sublattice` lattices.
*/
class UniformProductLattice[L <: Lattice](val sublattice: L, n: Int) extends Lattice {
type Element = List[sublattice.Element]
val bottom: Element = List.fill(n)(sublattice.bottom)
def lub(x: Element, y: Element): Element = {
if (x.length != y.length)
error()
(x zip y).map { case (xc, yc) => sublattice.lub(xc, yc) }
}
private def error() = throw new IllegalArgumentException("products not of same length")
}
/**
* The flat lattice made of element of `X`.
* Top is greater than every other element, and Bottom is less than every other element.
* No additional ordering is defined.
*/
class FlatLattice[X] extends Lattice {
sealed trait FlatElement
case class FlatEl(el: X) extends FlatElement {
override def toString = el.toString
}
final case object Top extends FlatElement {
override def toString = "Top"
}
final case object Bot extends FlatElement {
override def toString = "Bot"
}
type Element = FlatElement
/**
* Wrap an element of `X` into an element of the flat lattice.
*/
implicit def wrap(a: X): Element = FlatEl(a)
/**
* Unwrap an element of the lattice to an element of `X`.
* If the element is Top or Bot then IllegalArgumentException is thrown.
* Note that this method is declared as implicit, so the conversion can be done automatically.
*/
implicit def unwrap(a: Element): X = a match {
case FlatEl(n) => n
case _ => throw new IllegalArgumentException(s"cannot unlift $a")
}
val bottom: Element = Bot
override val top: Element = Top
def lub(x: Element, y: Element): Element =
if (x == Bot || y == Top || x == y)
y
else if (y == Bot || x == Top)
x
else
Top
}
/**
* The product lattice made by `l1` and `l2`.
*/
class PairLattice[L1 <: Lattice, L2 <: Lattice](val sublattice1: L1, val sublattice2: L2) extends Lattice {
type Element = (sublattice1.Element, sublattice2.Element)
val bottom: Element = (sublattice1.bottom, sublattice2.bottom)
def lub(x: Element, y: Element): Element = (sublattice1.lub(x._1, y._1), sublattice2.lub(x._2, y._2))
}
/**
* A lattice of maps from the set `X` to the lattice `sublattice`.
* The set `X` is a subset of `A` and it is defined by the characteristic function `ch`, i.e. `a` is in `X` if and only if `ch(a)` returns true.
* Bottom is the default value.
*/
class MapLattice[A, +L <: Lattice](ch: A => Boolean, val sublattice: L) extends Lattice {
// note: 'ch' isn't used in the class, but having it as a class parameter avoids a lot of type annotations
type Element = Map[A, sublattice.Element] // TODO: replace this with a more type safe solution?
val bottom: Element = Map().withDefaultValue(sublattice.bottom)
def lub(x: Element, y: Element): Element =
x.keys.foldLeft(y)((m, a) => m + (a -> sublattice.lub(x(a), y(a)))).withDefaultValue(sublattice.bottom)
}
/**
* The powerset lattice of `X`, where `X` is the subset of `A` defined by the characteristic function `ch`, with subset ordering.
*/
class PowersetLattice[A](ch: A => Boolean) extends Lattice {
// note: 'ch' isn't used in the class, but having it as a class parameter avoids a lot of type annotations
type Element = Set[A]
val bottom: Element = ??? //<--- Complete here
def lub(x: Element, y: Element): Element = ??? //<--- Complete here
}
/**
* The powerset lattice of `X`, where `X` is the subset of `A` defined by the characteristic function `ch`, with superset ordering.
*/
class ReversePowersetLattice[A](s: Set[A]) extends Lattice {
type Element = Set[A]
val bottom: Element = s
def lub(x: Element, y: Element): Element = x intersect y
}
/**
* The lift lattice for `sublattice`.
* Supports implicit lifting and unlifting.
*/
class LiftLattice[+L <: Lattice](val sublattice: L) extends Lattice {
type Element = Lifted
sealed trait Lifted
case object Bottom extends Lifted {
override def toString = "LiftBot"
}
case class Lift(n: sublattice.Element) extends Lifted
val bottom: Element = Bottom
def lub(x: Element, y: Element): Element =
(x, y) match {
case (Bottom, t) => t
case (t, Bottom) => t
case (Lift(a), Lift(b)) => Lift(sublattice.lub(a, b))
}
/**
* Lift elements of the sublattice to this lattice.
* Note that this method is declared as implicit, so the conversion can be done automatically.
*/
implicit def lift(x: sublattice.Element): Element = Lift(x)
/**
* Un-lift elements of this lattice to the sublattice.
* Throws an IllegalArgumentException if trying to unlift the bottom element
* Note that this method is declared as implicit, so the conversion can be done automatically.
*/
implicit def unlift(x: Element): sublattice.Element = x match {
case Lift(s) => s
case Bottom => throw new IllegalArgumentException("Cannot unlift bottom")
}
}