Skip to content

Commit 3218a08

Browse files
juliankotrbapakoito
authored andcommitted
Add Monoidal type class (#1327)
* Add Monoidal type class * Add Monoidal instances * Add Monoidal documentation * Run Semigroupal test in Monoidal tests * Fix Monoidal documentation * Remove unused import
1 parent 21f9767 commit 3218a08

File tree

12 files changed

+191
-8
lines changed

12 files changed

+191
-8
lines changed

modules/core/arrow-core-data/src/test/kotlin/arrow/core/OptionTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import arrow.core.extensions.option.applicative.applicative
88
import arrow.core.extensions.option.eq.eq
99
import arrow.core.extensions.option.hash.hash
1010
import arrow.core.extensions.option.monoid.monoid
11-
import arrow.core.extensions.option.semigroupal.semigroupal
11+
import arrow.core.extensions.option.monoidal.monoidal
1212
import arrow.core.extensions.option.show.show
1313
import arrow.core.extensions.tuple2.eq.eq
1414
import arrow.mtl.extensions.option.monadFilter.monadFilter
@@ -49,7 +49,7 @@ class OptionTest : UnitSpec() {
4949
TraverseFilterLaws.laws(Option.traverseFilter(), Option.applicative(), ::Some, Eq.any()),
5050
MonadFilterLaws.laws(Option.monadFilter(), ::Some, Eq.any()),
5151
HashLaws.laws(Option.hash(Int.hash()), Option.eq(Int.eq())) { it.some() },
52-
SemigroupalLaws.laws(Option.semigroupal(), ::Some, ::bijection, associativeSemigroupalEq)
52+
MonoidalLaws.laws(Option.monoidal(), ::Some, Eq.any(), ::bijection, associativeSemigroupalEq)
5353
)
5454

5555
"fromNullable should work for both null and non-null values of nullable types" {

modules/core/arrow-core-extensions/src/main/kotlin/arrow/core/extensions/option.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ interface OptionSemigroupal : Semigroupal<ForOption> {
3232
fb.fix().ap(this.map { a:A -> { b: B -> Tuple2(a,b)} })
3333
}
3434

35+
@extension
36+
interface OptionMonoidal : Monoidal<ForOption>, OptionSemigroupal {
37+
override fun <A> identity(): Kind<ForOption, A> = None
38+
}
39+
3540
@extension
3641
interface OptionMonoid<A> : Monoid<Option<A>>, OptionSemigroup<A> {
3742
override fun SG(): Semigroup<A>

modules/core/arrow-extras-data/src/test/kotlin/arrow/data/ListKTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import arrow.data.extensions.listk.applicative.applicative
1010
import arrow.data.extensions.listk.eq.eq
1111
import arrow.data.extensions.listk.hash.hash
1212
import arrow.data.extensions.listk.monoidK.monoidK
13+
import arrow.data.extensions.listk.monoidal.monoidal
1314
import arrow.data.extensions.listk.semigroupK.semigroupK
14-
import arrow.data.extensions.listk.semigroupal.semigroupal
1515
import arrow.data.extensions.listk.show.show
1616
import arrow.data.extensions.listk.traverse.traverse
1717
import arrow.mtl.extensions.listk.monadCombine.monadCombine
@@ -34,7 +34,7 @@ class ListKTest : UnitSpec() {
3434
testLaws(
3535
ShowLaws.laws(ListK.show(), eq) { listOf(it).k() },
3636
SemigroupKLaws.laws(ListK.semigroupK(), applicative, Eq.any()),
37-
SemigroupalLaws.laws(ListK.semigroupal(), { ListK.just(it) }, this::bijection, associativeSemigroupalEq),
37+
MonoidalLaws.laws(ListK.monoidal(), applicative, ListK.eq(Tuple2.eq(Int.eq(), Int.eq())), this::bijection, associativeSemigroupalEq),
3838
MonoidKLaws.laws(ListK.monoidK(), applicative, Eq.any()),
3939
TraverseLaws.laws(ListK.traverse(), applicative, { n: Int -> ListK(listOf(n)) }, Eq.any()),
4040
MonadCombineLaws.laws(ListK.monadCombine(),

modules/core/arrow-extras-data/src/test/kotlin/arrow/data/SequenceKTest.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import arrow.data.extensions.sequencek.hash.hash
1010
import arrow.data.extensions.sequencek.monad.monad
1111
import arrow.data.extensions.sequencek.monoid.monoid
1212
import arrow.data.extensions.sequencek.monoidK.monoidK
13-
import arrow.data.extensions.sequencek.semigroupal.semigroupal
13+
import arrow.data.extensions.sequencek.monoidal.monoidal
1414
import arrow.data.extensions.sequencek.traverse.traverse
1515
import arrow.test.UnitSpec
1616
import arrow.test.generators.sequenceK
@@ -36,6 +36,11 @@ class SequenceKTest : UnitSpec() {
3636
this.toList() == b.toList()
3737
}
3838

39+
val tuple2Eq: Eq<Kind<ForSequenceK, Tuple2<Int, Int>>> = object : Eq<Kind<ForSequenceK, Tuple2<Int, Int>>> {
40+
override fun Kind<ForSequenceK, Tuple2<Int, Int>>.eqv(b: Kind<ForSequenceK, Tuple2<Int, Int>>): Boolean =
41+
toList() == b.toList()
42+
}
43+
3944
val show: Show<Kind<ForSequenceK, Int>> = object : Show<Kind<ForSequenceK, Int>> {
4045
override fun Kind<ForSequenceK, Int>.show(): String =
4146
toList().toString()
@@ -46,7 +51,7 @@ class SequenceKTest : UnitSpec() {
4651
MonadLaws.laws(SequenceK.monad(), eq),
4752
MonoidKLaws.laws(SequenceK.monoidK(), SequenceK.applicative(), eq),
4853
MonoidLaws.laws(SequenceK.monoid(), Gen.sequenceK(Gen.int()), eq),
49-
SemigroupalLaws.laws(SequenceK.semigroupal(), { SequenceK.just(it) }, this::bijection, associativeSemigroupalEq),
54+
MonoidalLaws.laws(SequenceK.monoidal(), { SequenceK.just(it) }, tuple2Eq, this::bijection, associativeSemigroupalEq),
5055
TraverseLaws.laws(SequenceK.traverse(), SequenceK.applicative(), { n: Int -> SequenceK(sequenceOf(n)) }, eq),
5156
HashLaws.laws(SequenceK.hash(Int.hash()), SequenceK.eq(Int.eq())) { sequenceOf(it).k() }
5257
)

modules/core/arrow-extras-data/src/test/kotlin/arrow/data/SetKTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import arrow.data.extensions.setk.eq.eq
99
import arrow.data.extensions.setk.foldable.foldable
1010
import arrow.data.extensions.setk.hash.hash
1111
import arrow.data.extensions.setk.monoidK.monoidK
12+
import arrow.data.extensions.setk.monoidal.monoidal
1213
import arrow.data.extensions.setk.semigroupK.semigroupK
13-
import arrow.data.extensions.setk.semigroupal.semigroupal
1414
import arrow.data.extensions.setk.show.show
1515
import arrow.test.UnitSpec
1616
import arrow.test.laws.*
@@ -36,7 +36,7 @@ class SetKTest : UnitSpec() {
3636
testLaws(
3737
ShowLaws.laws(SetK.show(), EQ) { SetK.just(it) },
3838
SemigroupKLaws.laws(SetK.semigroupK(), { SetK.just(it) }, Eq.any()),
39-
SemigroupalLaws.laws(SetK.semigroupal(), { SetK.just(it) }, this::bijection, associativeSemigroupalEq),
39+
MonoidalLaws.laws(SetK.monoidal(), { SetK.just(it) }, Eq.any(), this::bijection, associativeSemigroupalEq),
4040
MonoidKLaws.laws(SetK.monoidK(), { SetK.just(it) }, Eq.any()),
4141
FoldableLaws.laws(SetK.foldable(), { SetK.just(it) }, Eq.any()),
4242
HashLaws.laws(SetK.hash(Int.hash()), SetK.eq(Int.eq())) { SetK.just(it) }

modules/core/arrow-extras-extensions/src/main/kotlin/arrow/data/extensions/listk.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ interface ListKSemigroupal : Semigroupal<ForListK> {
135135
fb.fix().ap(this.map { a:A -> { b: B -> Tuple2(a,b)} })
136136
}
137137

138+
@extension
139+
interface ListKMonoidal : Monoidal<ForListK>, ListKSemigroupal {
140+
override fun <A> identity(): Kind<ForListK, A> = ListK.empty()
141+
}
142+
138143
@extension
139144
interface ListKMonoidK : MonoidK<ForListK> {
140145
override fun <A> empty(): ListK<A> =

modules/core/arrow-extras-extensions/src/main/kotlin/arrow/data/extensions/sequence.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ interface SequenceKSemigroupal : Semigroupal<ForSequenceK> {
2323
fb.fix().ap(this.map { a: A -> { b: B -> Tuple2(a, b)} })
2424
}
2525

26+
@extension
27+
interface SequenceKMonoidal : Monoidal<ForSequenceK>, SequenceKSemigroupal {
28+
override fun <A> identity(): Kind<ForSequenceK, A> = SequenceK.empty()
29+
}
30+
2631
@extension
2732
interface SequenceKMonoid<A> : Monoid<SequenceK<A>> {
2833
override fun SequenceK<A>.combine(b: SequenceK<A>): SequenceK<A> = (this.sequence + b.sequence).k()

modules/core/arrow-extras-extensions/src/main/kotlin/arrow/data/extensions/setk.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ interface SetKSemigroupal: Semigroupal<ForSetK> {
6666
fb.fix().flatMap { b -> this.fix().map { a -> Tuple2(a,b) } }.toSet().k()
6767
}
6868

69+
@extension
70+
interface SetKMonoidal : Monoidal<ForSetK>, SetKSemigroupal {
71+
override fun <A> identity(): Kind<ForSetK, A> = SetK.empty()
72+
}
73+
6974
@extension
7075
interface SetKMonoidK : MonoidK<ForSetK> {
7176
override fun <A> empty(): SetK<A> =
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package arrow.test.laws
2+
3+
import arrow.Kind
4+
import arrow.core.Tuple2
5+
import arrow.typeclasses.Applicative
6+
import arrow.typeclasses.Eq
7+
import arrow.typeclasses.Monoidal
8+
import io.kotlintest.properties.Gen
9+
import io.kotlintest.properties.forAll
10+
11+
object MonoidalLaws {
12+
13+
fun <F> laws(
14+
MDAL: Monoidal<F>,
15+
AP: Applicative<F>,
16+
EQ: Eq<Kind<F, Tuple2<Int, Int>>>,
17+
BIJECTION: (Kind<F, Tuple2<Tuple2<Int, Int>, Int>>) -> (Kind<F, Tuple2<Int, Tuple2<Int, Int>>>),
18+
ASSOCIATIVE_SEMIGROUPAL_EQ: Eq<Kind<F, Tuple2<Int, Tuple2<Int, Int>>>>
19+
): List<Law> =
20+
SemigroupalLaws.laws(MDAL, AP::just, BIJECTION, ASSOCIATIVE_SEMIGROUPAL_EQ) + listOf(
21+
Law("Monoidal Laws: Left identity") { MDAL.monoidalLeftIdentity(AP::just, EQ) },
22+
Law("Monoidal Laws: Right identity") { MDAL.monoidalRightIdentity(AP::just, EQ) }
23+
)
24+
25+
fun <F> laws(
26+
MDAL: Monoidal<F>,
27+
f: (Int) -> Kind<F, Int>,
28+
EQ: Eq<Kind<F, Tuple2<Int, Int>>>,
29+
BIJECTION: (Kind<F, Tuple2<Tuple2<Int, Int>, Int>>) -> (Kind<F, Tuple2<Int, Tuple2<Int, Int>>>),
30+
ASSOCIATIVE_SEMIGROUPAL_EQ: Eq<Kind<F, Tuple2<Int, Tuple2<Int, Int>>>>
31+
): List<Law> =
32+
SemigroupalLaws.laws(MDAL, f, BIJECTION, ASSOCIATIVE_SEMIGROUPAL_EQ) + listOf(
33+
Law("Monoidal Laws: Left identity") { MDAL.monoidalLeftIdentity(f, EQ) },
34+
Law("Monoidal Laws: Right identity") { MDAL.monoidalRightIdentity(f, EQ) }
35+
)
36+
37+
private fun <F> Monoidal<F>.monoidalLeftIdentity(f: (Int) -> Kind<F, Int>, EQ: Eq<Kind<F, Tuple2<Int, Int>>>): Unit =
38+
forAll(Gen.int().map(f)) { fa: Kind<F, Int> ->
39+
identity<Int>().product(fa).equalUnderTheLaw(identity(), EQ)
40+
}
41+
42+
private fun <F> Monoidal<F>.monoidalRightIdentity(f: (Int) -> Kind<F, Int>, EQ: Eq<Kind<F, Tuple2<Int, Int>>>): Unit =
43+
forAll(Gen.int().map(f)) { fa: Kind<F, Int> ->
44+
fa.product(identity<Int>()).equalUnderTheLaw(identity(), EQ)
45+
}
46+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package arrow.typeclasses
2+
3+
import arrow.Kind
4+
import arrow.core.Option
5+
6+
/**
7+
* ank_macro_hierarchy(arrow.typeclasses.Monoidal)
8+
*
9+
* The [Monoidal] type class adds an identity element to the [Semigroupal] type class by defining the function [identity].
10+
*
11+
* [identity] returns a specific identity `Kind<F, A>` value for a given type [F] and [A].
12+
*
13+
* This type class complies with the following law:
14+
*
15+
* ```kotlin
16+
* fa.product(identity) == identity.product(fa) == identity
17+
* ```
18+
*
19+
* In addition, the laws of the [Semigroupal] type class also apply.
20+
*
21+
* Currently, [Monoidal] instances are defined for [Option], [ListK], [SequenceK] and [SetK].
22+
*
23+
* ### Examples
24+
*
25+
* ```kotlin:ank:playground:extension
26+
* _imports_
27+
*
28+
* fun main(args: Array<String>) {
29+
* val result =
30+
* //sampleStart
31+
* _extensionFactory_
32+
* //sampleEnd
33+
* println(result)
34+
* }
35+
* ```
36+
*
37+
* The following examples show the identity elements for the already defined [Monoidal] instances:
38+
*
39+
* ```kotlin:ank:playground
40+
* import arrow.core.Option
41+
* import arrow.core.extensions.option.monoidal.monoidal
42+
*
43+
* fun main(args: Array<String>) {
44+
* val result =
45+
* //sampleStart
46+
* Option.monoidal().run {
47+
* identity<Any>()
48+
* }
49+
* //sampleEnd
50+
* println(result)
51+
* }
52+
* ```
53+
*
54+
* ```kotlin:ank:playground
55+
* import arrow.data.ListK
56+
* import arrow.data.extensions.listk.monoidal.monoidal
57+
*
58+
* fun main(args: Array<String>) {
59+
* val result =
60+
* //sampleStart
61+
* ListK.monoidal().run {
62+
* identity<Any>()
63+
* }
64+
* //sampleEnd
65+
* println(result)
66+
* }
67+
* ```
68+
*
69+
* ```kotlin:ank:playground
70+
* import arrow.data.SetK
71+
* import arrow.data.extensions.setk.monoidal.monoidal
72+
*
73+
* fun main(args: Array<String>) {
74+
* val result =
75+
* //sampleStart
76+
* SetK.monoidal().run {
77+
* identity<Any>()
78+
* }
79+
* //sampleEnd
80+
* println(result)
81+
* }
82+
* ```
83+
*
84+
* ```kotlin:ank:playground
85+
* import arrow.data.SequenceK
86+
* import arrow.data.extensions.sequencek.monoidal.monoidal
87+
*
88+
* fun main(args: Array<String>) {
89+
* val result =
90+
* //sampleStart
91+
* SequenceK.monoidal().run {
92+
* identity<Any>()
93+
* }
94+
* //sampleEnd
95+
* println(result)
96+
* }
97+
* ```
98+
*/
99+
interface Monoidal<F> : Semigroupal<F> {
100+
101+
/**
102+
* Given a type [A], create an "identity" for a F<A> value.
103+
*/
104+
fun <A> identity(): Kind<F, A>
105+
106+
companion object
107+
}

0 commit comments

Comments
 (0)