Skip to content

Commit 203b880

Browse files
authored
Merge pull request #428 from kategory/simon-semigroup-fix
Semigroup cannot combineAll
2 parents 7248cda + cdf4c00 commit 203b880

File tree

6 files changed

+43
-28
lines changed

6 files changed

+43
-28
lines changed

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

+13
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@ interface Monoid<A> : Semigroup<A>, Typeclass {
66
*/
77
fun empty(): A
88

9+
/**
10+
* Combine an array of [A] values.
11+
*/
12+
fun combineAll(vararg elems: A): A = combineAll(elems.asList())
13+
14+
/**
15+
* Combine a collection of [A] values.
16+
*/
17+
fun combineAll(elems: Collection<A>): A =
18+
if (elems.isEmpty()) empty() else elems.reduce { a, b -> combine(a, b) }
19+
920
}
1021

1122
inline fun <reified A> A.empty(FT: Monoid<A> = monoid()): A = FT.empty()
1223

24+
inline fun <reified A> Collection<A>.combineAll(FT: Monoid<A> = monoid()): A = FT.combineAll(this)
25+
1326
inline fun <reified A> monoid(): Monoid<A> = instance(InstanceParametrizedType(Monoid::class.java, listOf(typeLiteral<A>())))

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

+1-12
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,8 @@ interface Semigroup<A> : Typeclass {
88

99
fun maybeCombine(a: A, b: A?): A = Option.fromNullable(b).fold({ a }, { this.combine(a, it) })
1010

11-
/**
12-
* Combine an array of [A] values.
13-
*/
14-
fun combineAll(vararg elems: A): A = combineAll(elems.asList())
15-
16-
/**
17-
* Combine a collection of [A] values.
18-
*/
19-
fun combineAll(elems: Collection<A>): A = elems.reduce { a, b -> combine(a, b) }
2011
}
2112

22-
inline fun <reified A> A.combine(FT: Semigroup<A> = semigroup(), b: A): A = FT.combine(this, b)
23-
24-
inline fun <reified A> Collection<A>.combineAll(FT: Semigroup<A> = semigroup()): A = FT.combineAll(this)
13+
inline fun <reified A> A.combine(b: A, FT: Semigroup<A> = semigroup()): A = FT.combine(this, b)
2514

2615
inline fun <reified A> semigroup(): Semigroup<A> = instance(InstanceParametrizedType(Semigroup::class.java, listOf(typeLiteral<A>())))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package kategory.typeclass
2+
3+
import io.kotlintest.KTestJUnitRunner
4+
import io.kotlintest.properties.forAll
5+
import kategory.UnitSpec
6+
import kategory.combineAll
7+
import org.junit.runner.RunWith
8+
9+
@RunWith(KTestJUnitRunner::class)
10+
class MonoidTest : UnitSpec() {
11+
12+
init {
13+
14+
"Combining all for a list of ints should be same as sum" {
15+
forAll({ ints: List<Int> ->
16+
ints.combineAll() == ints.sum()
17+
})
18+
}
19+
20+
}
21+
22+
}

kategory-docs/docs/docs/typeclasses/monoid/README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ StringMonoid.empty()
3131
listOf("K", "Λ", "T", "E", "G", "O", "R", "Y").combineAll()
3232
```
3333

34+
```kotlin:ank
35+
listOf<Option<Int>>(Option.Some(1), Option.Some(1)).combineAll()
36+
```
37+
3438
The advantage of using these type class provided methods, rather than the specific ones for each type, is that we can compose monoids to allow us to operate on more complex types, e.g.
3539

3640
This is also true if we define our own instances. As an example, let's use `Foldable`'s `foldMap`, which maps over values accumulating the results, using the available `Monoid` for the type mapped onto.
@@ -57,7 +61,7 @@ To use this with a function that produces a tuple, we can define a Monoid for a
5761
fun <A, B> monoidTuple(MA: Monoid<A>, MB: Monoid<B>): Monoid<Tuple2<A, B>> =
5862
object: Monoid<Tuple2<A, B>> {
5963
override fun combine(x: Tuple2<A, B>, y: Tuple2<A, B>): Tuple2<A, B> {
60-
println("monoitTuple#combine receiving ---> x: $x, y: $y")
64+
println("monoidTuple#combine receiving ---> x: $x, y: $y")
6165
val (xa, xb) = x
6266
val (ya, yb) = y
6367
return Tuple2(MA.combine(xa, ya), MB.combine(xb, yb))

kategory-docs/docs/docs/typeclasses/semigroup/README.md

-14
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,4 @@ Option.monoid<Int>().combine(Option(1), Option.None)
4949

5050
Many of these types have methods defined directly on them, which allow for such combining, e.g. `+` on `List`, but the value of having a `Semigroup` typeclass available is that these compose.
5151

52-
There is also extension syntax available for Semigroup.
53-
54-
```kotlin:ank
55-
listOf(Option(1), Option(2)).combineAll(Option.semigroup<Int>())
56-
```
57-
58-
```kotlin:ank
59-
listOf(1, 2, 3).combineAll()
60-
```
61-
62-
```kotlin:ank
63-
listOf("K", "Λ", "T", "E", "G", "O", "R", "Y").combineAll()
64-
```
65-
6652
Contents partially adapted from [Scala Exercises Cat's Semigroup Tutorial](https://www.scala-exercises.org/cats/semigroup)

kategory-test/src/main/kotlin/kategory/laws/TraversalLaws.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ object TraversalLaws {
3131
forAll(aGen, funcGen, { a, f ->
3232
traversal.getAll(traversal.modify(a, f))
3333
.zip(traversal.getAll(a).map(f), { b1, b2 -> EQB.eqv(b1, b2) })
34-
.combineAll(object : Semigroup<Boolean> {
34+
.combineAll(object : Monoid<Boolean> {
35+
override fun empty(): Boolean = true
3536
override fun combine(a: Boolean, b: Boolean): Boolean = a && b
3637
})
3738
})

0 commit comments

Comments
 (0)