Skip to content

Commit 2be5198

Browse files
authored
Add immutable ordered finite 'map' and 'set' to the stdlib (#561)
I've been sitting on this code for almost ~~9~~ _13_ months now (started on Xmas 2023), it's about time we finalise it. This PR adds immutable ordered maps and sets based on weight balanced trees into the standard library. The `map` and `set` now work on all main backends: JS, LLVM, Chez. Progress ~~is **blocked**~~ was blocked on: 1. ~~very annoying inliner bug(s?) [#733]~~ _resolved_ 🥳 2. ~~missing comparisons on strings on LLVM [#748]~~ _resolved_ 🎉
1 parent e4be2f9 commit 2be5198

19 files changed

+1640
-11
lines changed

examples/stdlib/map/cache.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Get key1: value1
2+
Get key2: value2
3+
Get key1: newValue1
4+
Get key2: value2
5+
After cleaning:
6+
Get key1: newValue1
7+
Get key2: Not found

examples/stdlib/map/cache.effekt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import map
2+
import bytearray
3+
4+
record CacheEntry[V](value: V, timestamp: Int)
5+
6+
effect time(): Int
7+
8+
def main() = {
9+
var currentTime = 0
10+
try {
11+
var cache: Map[String, CacheEntry[String]] = map::empty(compareStringBytes)
12+
val maxAge = 8
13+
14+
def cachePut(key: String, value: String): Unit =
15+
cache = cache.put(key, CacheEntry(value, do time()))
16+
17+
def cacheGet(key: String): Option[String] =
18+
cache.get(key) match {
19+
case Some(entry) and do time() - entry.timestamp < maxAge => Some(entry.value)
20+
case _ => None()
21+
}
22+
23+
def cleanExpiredEntries(): Unit = {
24+
val currentTime = do time()
25+
cache = cache.filter { (_, entry) =>
26+
currentTime - entry.timestamp < maxAge
27+
}
28+
}
29+
30+
cachePut("key1", "value1")
31+
cachePut("key2", "value2")
32+
33+
println("Get key1: " ++ cacheGet("key1").getOrElse { "Not found" })
34+
println("Get key2: " ++ cacheGet("key2").getOrElse { "Not found" })
35+
36+
cachePut("key1", "newValue1")
37+
38+
println("Get key1: " ++ cacheGet("key1").getOrElse { "Not found" })
39+
println("Get key2: " ++ cacheGet("key2").getOrElse { "Not found" })
40+
41+
cleanExpiredEntries()
42+
43+
println("After cleaning:")
44+
println("Get key1: " ++ cacheGet("key1").getOrElse { "Not found" })
45+
println("Get key2: " ++ cacheGet("key2").getOrElse { "Not found" })
46+
} with time {
47+
currentTime = currentTime + 1
48+
resume(currentTime)
49+
}
50+
}

examples/stdlib/map/counter.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ask: 3
2+
can: 3
3+
you: 3
4+
see: 0
5+
do: 4
6+
Effekt: 2
7+
[Effekt → 2, and → 1, ask → 3, but → 1, can → 3, do → 4, fellow → 2, for → 4, language → 2, man → 1, my → 2, not → 2, of → 2, programmers → 2, programs → 1, so → 1, the → 2, together → 1, we → 1, what → 4, will → 1, world → 1, you → 3, your → 2]

examples/stdlib/map/counter.effekt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import map
2+
import bytearray
3+
4+
def counter(words: List[String]): Map[String, Int] = {
5+
var m: Map[String, Int] = map::empty(compareStringBytes);
6+
7+
list::foreach(words) { word =>
8+
m = m.putWithKey(word, 1) { (_, n1, n2) => n1 + n2 }
9+
}
10+
11+
m
12+
}
13+
14+
def main() = {
15+
// John F. Kennedy's Inaugural Address, Jan 20, 1961; modified for Effekt
16+
val speech: List[String] = [
17+
"and", "so", "my", "fellow", "Effekt", "programmers",
18+
"ask", "not", "what", "your", "language", "can", "do", "for", "you",
19+
"ask", "what", "you", "can", "do", "for", "your", "language",
20+
"my", "fellow", "programmers", "of", "the", "world",
21+
"ask", "not", "what", "Effekt", "will", "do", "for", "you",
22+
"but", "what", "together", "we", "can", "do", "for", "the", "programs", "of", "man"
23+
]
24+
25+
val ctr: Map[String, Int] = counter(speech)
26+
27+
def test(word: String) = {
28+
val count = ctr.getOrElse(word) { 0 }
29+
println(word ++ ": " ++ count.show)
30+
}
31+
32+
test("ask")
33+
test("can")
34+
test("you")
35+
test("see")
36+
test("do")
37+
test("Effekt")
38+
39+
println(map::internal::prettyPairs(ctr.toList) { s => show(s) } { n => show(n) })
40+
}

examples/stdlib/map/map.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[0 → Hello, 1 → World, 2 → Woo!]
2+
[-1 → Hullo, 0 → Hello, 1 → World, 2 → Woo!]
3+
[-10 → EY, -1 → Hullo, 0 → Hello, 1 → World, 2 → Woo!]
4+
[0 → Hello, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!]
5+
[-1 → Huh?!, 0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!]
6+
[-1 → Huh?!, 0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!]
7+
[0 → Hello, 1 → World, 2 → Woo!]

examples/stdlib/map/map.effekt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import map
2+
3+
def main() = {
4+
val l = [(0, "Hello"), (1, "World"), (2, "Woo!")]
5+
6+
val m = map::fromList(l, compareInt)
7+
println(map::internal::prettyPairs(m.toList) { n => show(n) } { s => show(s) })
8+
9+
val m2 = m.put(-1, "Hullo")
10+
println(map::internal::prettyPairs(m2.toList) { n => show(n) } { s => show(s) })
11+
12+
val m3 = m2.put(-10, "EY")
13+
println(map::internal::prettyPairs(m3.toList) { n => show(n) } { s => show(s) })
14+
15+
// ...
16+
17+
val m4 = m.delete(1).put(42, "Whole new world!").put(100, "Big").put(1000, "Bigger").put(10000, "Biggest!")
18+
println(map::internal::prettyPairs(m4.toList) { n => show(n) } { s => show(s) })
19+
20+
val m5 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m4.toList.reverse)), compareInt)
21+
println(map::internal::prettyPairs(m5.toList) { n => show(n) } { s => show(s) })
22+
23+
val m6: Map[Int, String] = m5.toList.fromList(compareInt)
24+
println(map::internal::prettyPairs(m6.toList) { n => show(n) } { s => show(s) })
25+
26+
val nuMap = map::fromList(l.reverse, compareInt)
27+
println(map::internal::prettyPairs(nuMap.toList) { n => show(n) } { s => show(s) })
28+
}

examples/stdlib/map/minmax.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Min: 5 -> Five
2+
Max: 30 -> Thirty

examples/stdlib/map/minmax.effekt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import map
2+
3+
def show(opt: Option[(Int, String)]): String = opt match {
4+
case Some((k, v)) => k.show ++ " -> " ++ v
5+
case None() => "X"
6+
}
7+
8+
def main() = {
9+
val m = map::fromList([(10, "Ten"), (20, "Twenty"), (5, "Five"), (30, "Thirty")], compareInt)
10+
11+
val min: Option[(Int, String)] = m.getMin
12+
val max: Option[(Int, String)] = m.getMax
13+
14+
println("Min: " ++ min.show)
15+
println("Max: " ++ max.show)
16+
}

examples/stdlib/map/setops.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Union (keeping first value):
2+
[1 → apple, 2 → banana, 3 → cherry, 4 → elderberry]
3+
4+
Union (combining values):
5+
[1 → apple, 2 → banana/berry, 3 → cherry/date, 4 → elderberry]
6+
7+
Intersection (combining keys):
8+
[2 → banana-berry, 3 → cherry-date]

examples/stdlib/map/setops.effekt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import map
2+
3+
def main() = {
4+
// Create multiple maps
5+
val map1 = map::fromList([(1, "apple"), (2, "banana"), (3, "cherry")], compareInt)
6+
val map2 = map::fromList([(2, "berry"), (3, "date"), (4, "elderberry")], compareInt)
7+
8+
// Test union with different combine strategies
9+
println("Union (keeping first value):")
10+
println(map::internal::prettyPairs(map1.union(map2).toList) { n => show(n) } { s => show(s) })
11+
12+
println("\nUnion (combining values):")
13+
val combinedMap = map1.union(map2, compareInt) { (k, v1, v2) => v1 ++ "/" ++ v2 }
14+
println(map::internal::prettyPairs(combinedMap.toList) { n => show(n) } { s => show(s) })
15+
16+
// Test intersection
17+
println("\nIntersection (combining keys):")
18+
val intersectedMap = map1.intersection(map2, compareInt) { (k, v1, v2) => v1 ++ "-" ++ v2 }
19+
println(map::internal::prettyPairs(intersectedMap.toList) { n => show(n) } { s => show(s) })
20+
}

0 commit comments

Comments
 (0)