Skip to content

Commit ce0e13b

Browse files
authored
Merge pull request #261 from kategory/jorge-monadcombine
Add Alternative, Bifoldable, and MonadCombine
2 parents 2c84602 + 96a1cec commit ce0e13b

22 files changed

+267
-25
lines changed

kategory-core/src/main/kotlin/kategory/data/ListKW.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ package kategory
88
Foldable::class,
99
Traverse::class,
1010
SemigroupK::class,
11-
MonoidK::class)
11+
MonoidK::class,
12+
MonadCombine::class,
13+
FunctorFilter::class,
14+
MonadFilter::class)
1215
data class ListKW<out A> constructor(val list: List<A>) : ListKWKind<A>, List<A> by list {
1316

1417
fun <B> flatMap(f: (A) -> ListKWKind<B>): ListKW<B> = this.ev().list.flatMap { f(it).ev().list }.k()
@@ -39,6 +42,9 @@ data class ListKW<out A> constructor(val list: List<A>) : ListKWKind<A>, List<A>
3942
}
4043
}.ev()
4144

45+
fun <B> mapFilter(f: (A) -> Option<B>): ListKW<B> =
46+
flatMap({ a -> f(a).fold({ empty<B>() }, { pure(it) }) })
47+
4248
companion object {
4349

4450
fun <A> pure(a: A): ListKW<A> = listOf(a).k()

kategory-core/src/main/kotlin/kategory/data/StateT.kt

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class StateT<F, S, A>(
1515

1616
fun <F, S, A> invokeF(runF: StateTFunKind<F, S, A>): StateT<F, S, A> = StateT(runF)
1717

18+
fun <F, S, A> lift(fa: HK<F, A>, MF: Monad<F>): StateT<F, S, A> = StateT(MF.pure({ s -> MF.map(fa, { a -> Tuple2(s, a) }) }))
19+
1820
inline fun <reified F, S> functor(FF: Functor<F> = functor<F>()): StateTFunctorInstance<F, S> =
1921
StateTFunctorInstanceImplicits.instance(FF)
2022

@@ -30,6 +32,12 @@ class StateT<F, S, A>(
3032
inline fun <reified F, S> semigroupK(MF: Monad<F> = monad<F>(), SF: SemigroupK<F> = semigroupK<F>()): StateTSemigroupKInstance<F, S> =
3133
StateTSemigroupKInstanceImplicits.instance(MF, SF)
3234

35+
inline fun <reified F, S> monadCombine(MCF: MonadCombine<F> = monadCombine<F>()): StateTMonadCombineInstance<F, S> =
36+
StateTMonadCombineInstanceImplicits.instance(MCF)
37+
38+
inline fun <reified F, S, reified E> monadError(ME: MonadError<F, E> = monadError<F, E>()) : StateTMonadErrorInstance<F, S, E> =
39+
StateTMonadErrorImplicits.instance(ME)
40+
3341
fun <F, S> get(AF: Applicative<F>): StateT<F, S, S> = StateT(AF.pure({ s: S -> AF.pure(Tuple2(s, s)) }))
3442

3543
fun <F, S> set(s: S, AF: Applicative<F>): StateT<F, S, Unit> = StateT(AF.pure({ _: S -> AF.pure(Tuple2(s, Unit)) }))

kategory-core/src/main/kotlin/kategory/instances/OptionInstances.kt

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package kategory
22

3+
import kategory.Option.None
4+
import kategory.Option.Some
5+
36
interface OptionSemigroupInstance<A> : Semigroup<Option<A>> {
47

58
fun SG(): Semigroup<A>
69

710
override fun combine(a: Option<A>, b: Option<A>): Option<A> =
811
when (a) {
9-
is Option.Some<A> -> when (b) {
10-
is Option.Some<A> -> Option.Some(SG().combine(a.value, b.value))
11-
is Option.None -> b
12+
is Some<A> -> when (b) {
13+
is Some<A> -> Some(SG().combine(a.value, b.value))
14+
is None -> b
1215
}
13-
is Option.None -> a
16+
is None -> a
1417
}
1518
}
1619

kategory-core/src/main/kotlin/kategory/instances/StateTInstances.kt

+36
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,39 @@ object StateTSemigroupKInstanceImplicits {
9595
}
9696
}
9797

98+
interface StateTMonadCombineInstance<F, S> : MonadCombine<StateTKindPartial<F, S>>, StateTMonadInstance<F, S>, StateTSemigroupKInstance<F, S> {
99+
100+
override fun MF(): MonadCombine<F>
101+
override fun SF(): SemigroupK<F> = MF()
102+
103+
override fun <A> empty(): HK<StateTKindPartial<F, S>, A> = liftT(MF().empty())
104+
105+
fun <A> liftT(ma: HK<F, A>): StateT<F, S, A> = StateT(MF().pure({ s: S -> MF().map(ma, { a: A -> s toT a }) }))
106+
}
107+
108+
object StateTMonadCombineInstanceImplicits {
109+
@JvmStatic
110+
fun <F, S> instance(MCF: MonadCombine<F>): StateTMonadCombineInstance<F, S> = object : StateTMonadCombineInstance<F, S> {
111+
override fun FF(): Functor<F> = MCF
112+
override fun MF(): MonadCombine<F> = MCF
113+
114+
}
115+
}
116+
117+
interface StateTMonadErrorInstance<F, S, E> : StateTMonadInstance<F, S>, MonadError<StateTKindPartial<F, S>, E> {
118+
override fun MF(): MonadError<F, E>
119+
120+
override fun <A> raiseError(e: E): HK<StateTKindPartial<F, S>, A> = StateT.lift(MF().raiseError(e), MF())
121+
122+
override fun <A> handleErrorWith(fa: HK<StateTKindPartial<F, S>, A>, f: (E) -> HK<StateTKindPartial<F, S>, A>): StateT<F, S, A> =
123+
StateT(MF().pure({ s -> MF().handleErrorWith(fa.runM(s, MF()), { e -> f(e).runM(s, MF()) }) }))
124+
}
125+
126+
object StateTMonadErrorImplicits {
127+
@JvmStatic
128+
fun <F, S, E> instance(ME: MonadError<F, E>): StateTMonadErrorInstance<F, S, E> = object : StateTMonadErrorInstance<F, S, E> {
129+
override fun FF(): Functor<F> = ME
130+
override fun MF(): MonadError<F, E> = ME
131+
}
132+
}
133+
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
package kategory.typeclasses
2-
3-
import kategory.Applicative
4-
import kategory.MonoidK
5-
import kategory.Typeclass
1+
package kategory
62

73
interface Alternative<F> : Applicative<F>, MonoidK<F>, Typeclass
4+
5+
inline fun <reified F> alternative(): Alternative<F> = instance(InstanceParametrizedType(Alternative::class.java, listOf(F::class.java)))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package kategory
2+
3+
interface Bifoldable<F> : Typeclass {
4+
5+
fun <A, B, C> bifoldLeft(fab: HK2<F, A, B>, c: C, f: (C, A) -> C, g: (C, B) -> C): C
6+
7+
fun <A, B, C> bifoldRight(fab: HK2<F, A, B>, c: Eval<C>, f: (A, Eval<C>) -> Eval<C>, g: (B, Eval<C>) -> Eval<C>): Eval<C>
8+
9+
fun <A, B, C> bifoldMap(fab: HK2<F, A, B>, f: (A) -> C, g: (B) -> C, MC: Monoid<C>) =
10+
bifoldLeft(fab, MC.empty(), { c, a -> MC.combine(c, f(a)) }, { c, b -> MC.combine(c, g(b)) })
11+
}
12+
13+
inline fun <F, A, B, reified C> Bifoldable<in F>.bifoldMap(fab: HK2<F, A, B>, noinline f: (A) -> C, noinline g: (B) -> C, MC: Monoid<C> = monoid()) =
14+
bifoldMap(fab, f, g, MC)
15+
16+
inline fun <reified F> bifoldable(): Bifoldable<F> = instance(InstanceParametrizedType(Bifoldable::class.java, listOf(F::class.java)))

kategory-core/src/main/kotlin/kategory/typeclasses/Composed.kt

+39-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package kategory
22

3-
import kategory.typeclasses.Alternative
4-
53
/**
64
* https://www.youtube.com/watch?v=wvSP5qYiz4Y
75
*/
@@ -10,9 +8,15 @@ interface ComposedType<out F, out G>
108
@Suppress("UNCHECKED_CAST")
119
fun <F, G, A> HK<F, HK<G, A>>.lift(): HK<ComposedType<F, G>, A> = this as HK<ComposedType<F, G>, A>
1210

11+
@Suppress("UNCHECKED_CAST")
12+
fun <F, G, A, B> HK2<F, HK2<G, A, B>, HK2<G, A, B>>.liftB(): HK2<ComposedType<F, G>, A, B> = this as HK2<ComposedType<F, G>, A, B>
13+
1314
@Suppress("UNCHECKED_CAST")
1415
fun <F, G, A> HK<ComposedType<F, G>, A>.lower(): HK<F, HK<G, A>> = this as HK<F, HK<G, A>>
1516

17+
@Suppress("UNCHECKED_CAST")
18+
fun <F, G, A, B> HK2<ComposedType<F, G>, A, B>.lowerB(): HK2<F, HK2<G, A, B>, HK2<G, A, B>> = this as HK2<F, HK2<G, A, B>, HK2<G, A, B>>
19+
1620
interface ComposedFoldable<F, G> :
1721
Foldable<ComposedType<F, G>> {
1822

@@ -219,3 +223,36 @@ interface ComposedFunctorFilter<F, G> : FunctorFilter<ComposedType<F, G>>, Compo
219223

220224
inline fun <reified F, reified G> Functor<F>.composeFilter(FFG: FunctorFilter<G> = functorFilter()):
221225
FunctorFilter<ComposedType<F, G>> = ComposedFunctorFilter(this, FFG)
226+
227+
interface ComposedBifoldable<F, G> : Bifoldable<ComposedType<F, G>> {
228+
fun F(): Bifoldable<F>
229+
230+
fun G(): Bifoldable<G>
231+
232+
override fun <A, B, C> bifoldLeft(fab: HK2<ComposedType<F, G>, A, B>, c: C, f: (C, A) -> C, g: (C, B) -> C): C =
233+
F().bifoldLeft(fab.lowerB(), c,
234+
{ cc: C, gab: HK2<G, A, B> -> G().bifoldLeft(gab, cc, f, g) },
235+
{ cc: C, gab: HK2<G, A, B> -> G().bifoldLeft(gab, cc, f, g) })
236+
237+
override fun <A, B, C> bifoldRight(fab: HK2<ComposedType<F, G>, A, B>, c: Eval<C>, f: (A, Eval<C>) -> Eval<C>, g: (B, Eval<C>) -> Eval<C>): Eval<C> =
238+
F().bifoldRight(fab.lowerB(), c,
239+
{ gab: HK2<G, A, B>, cc: Eval<C> -> G().bifoldRight(gab, cc, f, g) },
240+
{ gab: HK2<G, A, B>, cc: Eval<C> -> G().bifoldRight(gab, cc, f, g) })
241+
242+
fun <A, B, C> bifoldLeftC(fab: HK2<F, HK2<G, A, B>, HK2<G, A, B>>, c: C, f: (C, A) -> C, g: (C, B) -> C): C =
243+
bifoldLeft(fab.liftB(), c, f, g)
244+
245+
fun <A, B, C> bifoldRightC(fab: HK2<F, HK2<G, A, B>, HK2<G, A, B>>, c: Eval<C>, f: (A, Eval<C>) -> Eval<C>, g: (B, Eval<C>) -> Eval<C>): Eval<C> =
246+
bifoldRight(fab.liftB(), c, f, g)
247+
248+
companion object {
249+
operator fun <F, G> invoke(BF: Bifoldable<F>, BG: Bifoldable<G>): ComposedBifoldable<F, G> =
250+
object : ComposedBifoldable<F, G> {
251+
override fun F(): Bifoldable<F> = BF
252+
253+
override fun G(): Bifoldable<G> = BG
254+
}
255+
}
256+
}
257+
258+
inline fun <reified F, reified G> Bifoldable<F>.compose(BG: Bifoldable<G> = bifoldable()): Bifoldable<ComposedType<F, G>> = ComposedBifoldable(this, BG)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package kategory
2+
3+
/**
4+
* The combination of a Monad with a MonoidK
5+
*/
6+
interface MonadCombine<F> : MonadFilter<F>, Alternative<F>, Typeclass {
7+
8+
fun <G, A> unite(fga: HK<F, HK<G, A>>, FG: Foldable<G>): HK<F, A> =
9+
flatMap(fga, { ga -> FG.foldL(ga, empty<A>(), { acc, a -> combineK(acc, pure(a)) }) })
10+
11+
fun <G, A, B> separate(fgab: HK<F, HK2<G, A, B>>, BFG: Bifoldable<G>): Tuple2<HK<F, A>, HK<F, B>> {
12+
val asep = flatMap(fgab, { gab -> BFG.bifoldMap(gab, { pure(it) }, { _ -> empty<A>() }, algebra<A>()) })
13+
val bsep = flatMap(fgab, { gab -> BFG.bifoldMap(gab, { _ -> empty<B>() }, { pure(it) }, algebra<B>()) })
14+
return Tuple2(asep, bsep)
15+
}
16+
}
17+
18+
inline fun <reified F> monadCombine(): MonadCombine<F> = instance(InstanceParametrizedType(MonadCombine::class.java, listOf(typeLiteral<F>())))
19+
20+
inline fun <F, reified G, A> MonadCombine<F>.uniteF(fga: HK<F, HK<G, A>>, FG: Foldable<G> = foldable()) = unite(fga, FG)
21+
22+
inline fun <F, reified G, A, B> MonadCombine<F>.separateF(fgab: HK<F, HK2<G, A, B>>, BFG: Bifoldable<G> = bifoldable()) = separate(fgab, BFG)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package kategory
2+
3+
import io.kotlintest.KTestJUnitRunner
4+
import org.junit.runner.RunWith
5+
6+
@RunWith(KTestJUnitRunner::class)
7+
class BifoldableTests : UnitSpec() {
8+
init {
9+
10+
val eitherBifoldable: Bifoldable<EitherHK> = object : Bifoldable<EitherHK> {
11+
override fun <A, B, C> bifoldLeft(fab: HK2<EitherHK, A, B>, c: C, f: (C, A) -> C, g: (C, B) -> C): C =
12+
when (fab) {
13+
is Either.Left -> f(c, fab.a)
14+
else -> g(c, (fab as Either.Right).b)
15+
}
16+
17+
override fun <A, B, C> bifoldRight(fab: HK2<EitherHK, A, B>, c: Eval<C>, f: (A, Eval<C>) -> Eval<C>, g: (B, Eval<C>) -> Eval<C>): Eval<C> =
18+
when (fab) {
19+
is Either.Left -> f(fab.a, c)
20+
else -> g((fab as Either.Right).b, c)
21+
}
22+
}
23+
24+
val eitherComposeEither = eitherBifoldable.compose(eitherBifoldable)
25+
26+
testLaws(BifoldableLaws.laws(eitherComposeEither, { cf: Int -> Either.Right(Either.Right(cf)).liftB() }, Eq.any()))
27+
}
28+
}

kategory-core/src/test/kotlin/kategory/data/ListKWTest.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package kategory
22

33
import io.kotlintest.KTestJUnitRunner
4+
import io.kotlintest.matchers.shouldNot
45
import io.kotlintest.matchers.shouldNotBe
56
import org.junit.runner.RunWith
67

@@ -19,11 +20,15 @@ class ListKWTest : UnitSpec() {
1920
semigroupK<ListKWHK>() shouldNotBe null
2021
semigroup<ListKW<Int>>() shouldNotBe null
2122
monoid<ListKW<Int>>() shouldNotBe null
23+
monoidK<ListKW<ListKWHK>>() shouldNotBe null
24+
monadCombine<ListKW<ListKWHK>>() shouldNotBe null
25+
functorFilter<ListKW<ListKWHK>>() shouldNotBe null
26+
monadFilter<ListKW<ListKWHK>>() shouldNotBe null
2227
}
2328

2429
testLaws(MonadLaws.laws(ListKW.monad(), Eq.any()))
2530
testLaws(SemigroupKLaws.laws(ListKW.semigroupK(), applicative, Eq.any()))
2631
testLaws(MonoidKLaws.laws(ListKW.monoidK(), applicative, Eq.any()))
2732
testLaws(TraverseLaws.laws(ListKW.traverse(), applicative, { n: Int -> ListKW(listOf(n)) }, Eq.any()))
2833
}
29-
}
34+
}

kategory-core/src/test/kotlin/kategory/data/OptionTTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package kategory
33
import io.kotlintest.KTestJUnitRunner
44
import io.kotlintest.matchers.shouldNotBe
55
import io.kotlintest.properties.forAll
6-
import kategory.laws.FunctorFilterLaws
76
import org.junit.runner.RunWith
87

98
@RunWith(KTestJUnitRunner::class)

kategory-core/src/test/kotlin/kategory/data/OptionTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import io.kotlintest.matchers.shouldNotBe
66
import io.kotlintest.properties.forAll
77
import kategory.Option.None
88
import kategory.Option.Some
9-
import kategory.laws.MonadFilterLaws
109
import org.junit.runner.RunWith
1110

1211
@RunWith(KTestJUnitRunner::class)
@@ -39,7 +38,7 @@ class OptionTest : UnitSpec() {
3938
})
4039
})
4140
}
42-
41+
4342
//testLaws(MonadErrorLaws.laws(monadError<OptionHK, Unit>(), Eq.any(), EQ_EITHER)) TODO reenable once the MonadErrorLaws are parametric to `E`
4443
testLaws(TraverseLaws.laws(Option.traverse(), Option.monad(), ::Some, Eq.any()))
4544
testLaws(MonadFilterLaws.laws(Option.monadFilter(), ::Some, Eq.any()))

kategory-core/src/test/kotlin/kategory/data/StateTTests.kt

+9
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,14 @@ class StateTTests : UnitSpec() {
3838
override fun eqv(a: HK<StateTKindPartial<ListKWHK, Int>, Int>, b: HK<StateTKindPartial<ListKWHK, Int>, Int>): Boolean =
3939
a.runM(1, ListKW.monad()) == b.runM(1, ListKW.monad())
4040
}))
41+
42+
testLaws(MonadCombineLaws.laws(StateT.monadCombine<ListKWHK, Int>(ListKW.monadCombine()),
43+
{ StateT.lift(ListKW.pure(it), ListKW.monad()) },
44+
{ StateT.lift(ListKW.pure({ s: Int -> s * 2 }), ListKW.monad()) },
45+
object : Eq<HK<StateTKindPartial<ListKWHK, Int>, Int>> {
46+
override fun eqv(a: HK<StateTKindPartial<ListKWHK, Int>, Int>, b: HK<StateTKindPartial<ListKWHK, Int>, Int>): Boolean =
47+
a.runM(1, ListKW.monad()) == b.runM(1, ListKW.monad())
48+
}))
49+
4150
}
4251
}

kategory-core/src/test/kotlin/kategory/data/WriterTTest.kt

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package kategory
22

33
import io.kotlintest.KTestJUnitRunner
44
import io.kotlintest.matchers.shouldNotBe
5-
import kategory.laws.FunctorFilterLaws
6-
import kategory.laws.MonadFilterLaws
75
import org.junit.runner.RunWith
86

97
@RunWith(KTestJUnitRunner::class)

kategory-core/src/test/kotlin/kategory/instances/ComposedInstancesTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package kategory.instances
22

33
import io.kotlintest.KTestJUnitRunner
44
import kategory.*
5-
import kategory.laws.FunctorFilterLaws
65
import org.junit.runner.RunWith
76

87
typealias OptionTNel = HK<OptionTKindPartial<NonEmptyListHK>, Int>
@@ -42,4 +41,4 @@ class ComposedInstancesTest : UnitSpec() {
4241
testLaws(SemigroupKLaws.laws(ComposedSemigroupK<ListKWHK, OptionHK>(ListKW.semigroupK()), ComposedApplicative(ListKW.applicative(), Option.applicative()), EQ_LKW_OPTION))
4342
testLaws(MonoidKLaws.laws(ComposedMonoidK<ListKWHK, OptionHK>(ListKW.monoidK()), ComposedApplicative(ListKW.applicative(), Option.applicative()), EQ_LKW_OPTION))
4443
}
45-
}
44+
}

kategory-core/src/test/kotlin/kategory/instances/StringInstancesTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package kategory.instances
33
import io.kotlintest.KTestJUnitRunner
44
import io.kotlintest.matchers.shouldNotBe
55
import kategory.*
6-
import kategory.laws.FunctorFilterLaws
76
import org.junit.runner.RunWith
87

98
@RunWith(KTestJUnitRunner::class)
@@ -14,4 +13,4 @@ class StringInstancesTest : UnitSpec() {
1413
monoid<String>() shouldNotBe null
1514
}
1615
}
17-
}
16+
}

kategory-test/src/main/kotlin/kategory/generators/Generators.kt

+6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ inline fun <F, A> genConstructor(valueGen: Gen<A>, crossinline cf: (A) -> HK<F,
4747
cf(valueGen.generate())
4848
}
4949

50+
inline fun <F, A, B> genConstructor2(valueGen: Gen<A>, crossinline ff: (A) -> HK<F, (A) -> B>): Gen<HK<F, (A) -> B>> =
51+
object : Gen<HK<F, (A) -> B>> {
52+
override fun generate(): HK<F, (A) -> B> =
53+
ff(valueGen.generate())
54+
}
55+
5056
fun genIntSmall(): Gen<Int> =
5157
Gen.oneOf(Gen.negativeIntegers(), Gen.choose(0, Int.MAX_VALUE / 10000))
5258

0 commit comments

Comments
 (0)