Skip to content

Commit 185cf36

Browse files
Cotelraulraja
authored andcommitted
Add wrapper for SortedMap (#451)
* First approach to implement SortedMapKW (tests in progress) * SortedMapKW tests passing * Using `mapOf` instead of `sortedMapOf` * Changing Type Parameters to follow Kategory's convention * Using fold instead of when * Adding MonoidLaws and SemigroupLaws
1 parent a88e4d1 commit 185cf36

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package kategory
2+
3+
import java.util.*
4+
5+
@higherkind
6+
data class SortedMapKW<A: Comparable<A>, B>(val map: SortedMap<A, B>) : SortedMapKWKind<A, B>, SortedMap<A, B> by map {
7+
8+
fun <C> map(f: (B) -> C): SortedMapKW<A, C> =
9+
this.map.map { it.key to f(it.value) }.toMap().toSortedMap().k()
10+
11+
fun <C, Z> map2(fc: SortedMapKW<A, C>, f: (B, C) -> Z): SortedMapKW<A, Z> =
12+
if (fc.isEmpty()) sortedMapOf<A, Z>().k()
13+
else this.map.flatMap { (a, b) ->
14+
fc.getOption(a).map { Tuple2(a, f(b, it)) }.k().asIterable()
15+
}.k()
16+
17+
fun <C, Z> map2Eval(fc: Eval<SortedMapKW<A, C>>, f: (B, C) -> Z): Eval<SortedMapKW<A, Z>> =
18+
if (fc.isEmpty()) Eval.now(sortedMapOf<A, Z>().k())
19+
else fc.map { c -> this.map2(c, f) }
20+
21+
fun <C> ap(ff: SortedMapKW<A, (B) -> C>): SortedMapKW<A, C> =
22+
ff.flatMap { this.map(it) }
23+
24+
fun <C, Z> ap2(f: SortedMapKW<A, (B, C) -> Z>, fc: SortedMapKW<A, C>): SortedMap<A, Z> =
25+
f.map.flatMap { (k, f) ->
26+
this.flatMap { a -> fc.flatMap { c -> sortedMapOf(k to f(a, c)).k() } }
27+
.getOption(k).map { Tuple2(k, it) }.k().asIterable()
28+
}.k()
29+
30+
fun <C> flatMap(f: (B) -> SortedMapKW<A, C>): SortedMapKW<A, C> =
31+
this.map.flatMap { (k, v) ->
32+
f(v).getOption(k).map { Tuple2(k, it) }.k().asIterable()
33+
}.k()
34+
35+
fun <C> foldR(c: Eval<C>, f: (B, Eval<C>) -> Eval<C>): Eval<C> =
36+
this.map.values.iterator().iterateRight(c)(f)
37+
38+
fun <C> foldL(c: C, f: (C, B) -> C): C = this.map.values.fold(c, f)
39+
40+
fun <C> foldLeft(c: SortedMapKW<A, C>, f: (SortedMapKW<A, C>, Tuple2<A, B>) -> SortedMapKW<A, C>): SortedMapKW<A, C> =
41+
this.map.foldLeft(c) { m: SortedMap<A, C>, (a, b) -> f(m.k(), Tuple2(a, b)) }.k()
42+
43+
fun <G, C> traverse(f: (B) -> HK<G, C>, GA: Applicative<G>): HK<G, SortedMapKW<A, C>> =
44+
Foldable.iterateRight(this.map.iterator(), Eval.always { GA.pure(sortedMapOf<A, C>().k()) })({
45+
kv, lbuf ->
46+
GA.map2Eval(f(kv.value), lbuf) { (mapOf(kv.key to it.a).k() + it.b).toSortedMap().k() }
47+
}).value()
48+
49+
companion object
50+
}
51+
52+
fun <A: Comparable<A>, B> SortedMap<A, B>.k(): SortedMapKW<A, B> = SortedMapKW(this)
53+
54+
fun <A: Comparable<A>, B> Option<Tuple2<A, B>>.k(): SortedMapKW<A, B> = this.fold(
55+
{ sortedMapOf<A, B>().k() },
56+
{ mapEntry -> sortedMapOf<A, B>(mapEntry.a to mapEntry.b).k() }
57+
)
58+
59+
fun <A: Comparable<A>, B> List<Map.Entry<A, B>>.k(): SortedMapKW<A, B> =
60+
this.map { it.key to it.value }.toMap().toSortedMap().k()
61+
62+
fun <A, B> SortedMap<A, B>.getOption(k: A): Option<B> = Option.fromNullable(this[k])
63+
64+
fun <A: Comparable<A>, B> SortedMapKW<A, B>.updated(k: A, value: B): SortedMapKW<A, B> {
65+
val sortedMutableMap = this.toSortedMap()
66+
sortedMutableMap.put(k, value)
67+
68+
return sortedMutableMap.k()
69+
}
70+
71+
fun <A, B, C> SortedMap<A, B>.foldLeft(b: SortedMap<A, C>, f: (SortedMap<A, C>, Map.Entry<A, B>) -> SortedMap<A, C>): SortedMap<A, C> {
72+
var result = b
73+
this.forEach { result = f(result, it) }
74+
return result
75+
}
76+
77+
fun <A: Comparable<A>, B, C> SortedMap<A, B>.foldRight(b: SortedMap<A, C>, f: (Map.Entry<A, B>, SortedMap<A, C>) -> SortedMap<A, C>): SortedMap<A, C> =
78+
this.entries.reversed().k().map.foldLeft(b) { x: SortedMap<A, C>, y -> f(y, x) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package kategory
2+
3+
@instance(SortedMapKW::class)
4+
interface SortedMapKWFunctorInstance<A: Comparable<A>> : Functor<SortedMapKWKindPartial<A>> {
5+
override fun <B, C> map(fb: HK<SortedMapKWKindPartial<A>, B>, f: (B) -> C): SortedMapKW<A, C> =
6+
fb.ev().map(f)
7+
}
8+
9+
@instance(SortedMapKW::class)
10+
interface SortedMapKWFoldableInstance<A: Comparable<A>> : Foldable<SortedMapKWKindPartial<A>> {
11+
override fun <B, C> foldL(fb: HK<SortedMapKWKindPartial<A>, B>, c: C, f: (C, B) -> C): C =
12+
fb.ev().foldL(c, f)
13+
14+
override fun <B, C> foldR(fb: HK<SortedMapKWKindPartial<A>, B>, lc: Eval<C>, f: (B, Eval<C>) -> Eval<C>): Eval<C> =
15+
fb.ev().foldR(lc, f)
16+
}
17+
18+
@instance(SortedMapKW::class)
19+
interface SortedMapKWTraverseInstance<A: Comparable<A>> : SortedMapKWFoldableInstance<A>, Traverse<SortedMapKWKindPartial<A>> {
20+
override fun <G, B, C> traverse(fb: HK<SortedMapKWKindPartial<A>, B>, f: (B) -> HK<G, C>, GA: Applicative<G>): HK<G, HK<SortedMapKWKindPartial<A>, C>> =
21+
fb.ev().traverse(f, GA)
22+
}
23+
24+
@instance(SortedMapKW::class)
25+
interface SortedMapKWSemigroupInstance<A: Comparable<A>, B> : Semigroup<SortedMapKWKind<A, B>> {
26+
fun SG(): Semigroup<B>
27+
28+
override fun combine(a: SortedMapKWKind<A, B>, b: SortedMapKWKind<A, B>): SortedMapKWKind<A, B> =
29+
if (a.ev().size < b.ev().size) a.ev().foldLeft<B> (b.ev(), { my, (k, b) ->
30+
my.updated(k, SG().maybeCombine(b, my[k]))
31+
})
32+
else b.ev().foldLeft<B> (a.ev(), { my: SortedMapKW<A, B>, (k, a) -> my.updated(k, SG().maybeCombine(a, my[k])) })
33+
}
34+
35+
@instance(SortedMapKW::class)
36+
interface SortedMapKWMonoidInstance<A: Comparable<A>, B> : SortedMapKWSemigroupInstance<A, B>, Monoid<SortedMapKWKind<A, B>> {
37+
override fun empty(): SortedMapKW<A, B> = sortedMapOf<A, B>().k()
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package kategory
2+
3+
import io.kotlintest.KTestJUnitRunner
4+
import io.kotlintest.matchers.shouldNotBe
5+
import io.kotlintest.properties.forAll
6+
import org.junit.runner.RunWith
7+
8+
@RunWith(KTestJUnitRunner::class)
9+
class SortedMapKWTest : UnitSpec() {
10+
11+
val EQ: Eq<HK2<SortedMapKWHK, String, Int>> = object : Eq<HK2<SortedMapKWHK, String, Int>> {
12+
override fun eqv(a: HK2<SortedMapKWHK, String, Int>, b: HK2<SortedMapKWHK, String, Int>): Boolean =
13+
a.ev().get("key") == b.ev().get("key")
14+
}
15+
16+
init {
17+
18+
"instances can be resolved implicitly" {
19+
functor<SortedMapKWHK>() shouldNotBe null
20+
foldable<SortedMapKWHK>() shouldNotBe null
21+
traverse<SortedMapKWHK>() shouldNotBe null
22+
semigroup<SortedMapKWKind<String, Int>>() shouldNotBe null
23+
monoid<SortedMapKWKind<String, Int>>() shouldNotBe null
24+
}
25+
26+
27+
testLaws(
28+
MonoidLaws.laws(SortedMapKW.monoid<String, Int>(), sortedMapOf("key" to 1).k(), EQ),
29+
SemigroupLaws.laws(SortedMapKW.monoid<String, Int>(),
30+
sortedMapOf("key" to 1).k(),
31+
sortedMapOf("key" to 2).k(),
32+
sortedMapOf("key" to 3).k(),
33+
EQ),
34+
TraverseLaws.laws(
35+
SortedMapKW.traverse<String>(),
36+
SortedMapKW.traverse<String>(),
37+
{ a: Int -> sortedMapOf("key" to a).k() },
38+
EQ))
39+
40+
}
41+
42+
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package kategory
2+
3+
object MonoidLaws {
4+
5+
inline fun <reified F> laws(M: Monoid<F>, A: F, EQ: Eq<F>): List<Law> =
6+
listOf(
7+
Law("Monoid Laws: Left identity", { monoidLeftIdentity(M, A, EQ) }),
8+
Law("Monoid Laws: Right identity", { monoidRightIdentity(M, A, EQ) })
9+
)
10+
11+
inline fun <reified F> monoidLeftIdentity(M: Monoid<F>, A: F, EQ: Eq<F>): Boolean =
12+
M.combine(M.empty(), A).equalUnderTheLaw(A, EQ)
13+
14+
inline fun <reified F> monoidRightIdentity(M: Monoid<F>, A: F, EQ: Eq<F>): Boolean =
15+
M.combine(A, M.empty()).equalUnderTheLaw(A, EQ)
16+
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package kategory
2+
3+
object SemigroupLaws {
4+
5+
inline fun <reified F> laws(SG: Semigroup<F>, A: F, B: F, C:F, EQ: Eq<F>): List<Law> =
6+
listOf(Law("Semigroup: associativity", { semigroupAssociative(SG, A, B, C, EQ) }))
7+
8+
inline fun <reified F> semigroupAssociative(SG: Semigroup<F>, A: F, B: F, C: F, EQ: Eq<F>): Boolean =
9+
SG.combine(SG.combine(A, B), C).equalUnderTheLaw(SG.combine(A, SG.combine(B, C)), EQ)
10+
11+
}

0 commit comments

Comments
 (0)