Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Semigroup, Semiring and Monoid docs #2323

Merged
merged 9 commits into from Mar 27, 2021
Expand Up @@ -19,7 +19,8 @@ interface Semigroup<A> {
operator fun A.plus(b: A): A =
this.combine(b)

fun A.maybeCombine(b: A?): A = Option.fromNullable(b).fold({ this }, { combine(it) })
fun A.maybeCombine(b: A?): A =
b?.let { combine(it) } ?: this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃憣


companion object {
@JvmStatic
Expand Down
Expand Up @@ -11,11 +11,11 @@ import arrow.documented
* (a.combineMultiplicate(b)).combineMultiplicate(c) == a.combineMultiplicate(b.combineMultiplicate(c))
* ```
*
* The [one] value serves exactly like the [empty] function for an additive [Monoid], just adapted for the multiplicative
* The [one] function serves exactly like the [empty] function for an additive [Monoid], just adapted for the multiplicative
* version. This forms the following law:
*
* ```kotlin
* combineMultiplicate(x, one) == combineMultiplicate(one, x) == x
* a.combineMultiplicate(one()) == one().combineMultiplicate(a) == a
* ```
*
* Please note that the empty function has been renamed to [zero] to get a consistent naming style inside the semiring.
Expand All @@ -27,24 +27,26 @@ import arrow.documented
* Here a some examples:
*
* ```kotlin:ank:playground
* import arrow.core.extensions.*
* import arrow.core.int
* import arrow.typeclasses.Semiring
*
* fun main(args: Array<String>) {
* val result =
* //sampleStart
* Int.semiring().run { 1.combine(2) }
* Semiring.int().run { 1.combine(2) }
* //sampleEnd
* println(result)
* }
* ```
*
* ```kotlin:ank:playground
* import arrow.core.extensions.*
* import arrow.core.int
* import arrow.typeclasses.Semiring
*
* fun main(args: Array<String>) {
* val result =
* //sampleStart
* Int.semiring().run { 2.combineMultiplicate(3) }
* Semiring.int().run { 2.combineMultiplicate(3) }
* //sampleEnd
* println(result)
* }
Expand All @@ -53,29 +55,14 @@ import arrow.documented
* The type class `Semiring` also has support for the `+` `*` syntax:
*
* ```kotlin:ank:playground
* import arrow.core.Option
* import arrow.core.extensions.*
* import arrow.core.int
* import arrow.typeclasses.Semiring
*
* fun main(args: Array<String>) {
* val result =
* //sampleStart
* Int.semiring().run {
* 1 + 2
* }
* //sampleEnd
* println(result)
* }
* ```
*
* ```kotlin:ank:playground
* import arrow.core.Option
* import arrow.core.extensions.*
*
* fun main(args: Array<String>) {
* val result =
* //sampleStart
* Int.semiring().run {
* 2 * 3
* Semiring.int().run {
* 1 + 2 * 3
* }
* //sampleEnd
* println(result)
Expand Down
77 changes: 46 additions & 31 deletions arrow-site/docs/docs/arrow/typeclasses/monoid/README.md
Expand Up @@ -9,77 +9,92 @@ permalink: /arrow/typeclasses/monoid/



`Monoid` extends the `Semigroup` type class, adding an `empty` method to semigroup's `combine`. The empty method must return a value that, when combined with any other instance of that type, returns the other instance, i.e.,
`Monoid` extends the `Semigroup` type class, adding an `empty` function to semigroup's `combine`. The empty method must return a value that, when combined with any other instance of that type, returns the other instance, i.e.,

```kotlin
(combine(x, empty) == combine(empty, x) == x)
(a.combine(empty()) == empty().combine(a) == a)
```

For example, if we have a `Monoid<String>` with `combine` defined as string concatenation, then `empty = ""`.
For example, if we have a `Monoid<String>` with `combine` defined as string concatenation, then `empty() = ""`.

Having an empty defined allows us to combine all the elements of some potentially empty collection of `T` for which a `Monoid<T>` is defined and return a `T`, rather than an `Option<T>` as we have a sensible default to fall back to.
Having `empty` defined allows us to combine all the elements of some potentially empty collection of `T` for which a `Monoid<T>` is defined and return a `T`, rather than an `Option<T>` as we have a sensible default to fall back to.

And let's see the instance of Monoid<String> in action.
Let's see the instance of Monoid<String> in action:

```kotlin:ank
import arrow.*
import arrow.core.extensions.*
import arrow.typeclasses.*
import arrow.core.string
import arrow.typeclasses.Monoid

String.monoid().run { empty() }
Monoid.string().run { empty() }
```

```kotlin:ank
String.monoid().run {
listOf<String>("螞", "R", "R", "O", "W").combineAll()
Monoid.string().run {
listOf("螞", "R", "R", "O", "W").combineAll()
}
```

```kotlin:ank
import arrow.core.*
import arrow.core.extensions.option.monoid.*
import arrow.core.Option
import arrow.core.Some
import arrow.core.int
import arrow.core.option
import arrow.typeclasses.Monoid

Option.monoid(Int.monoid()).run { listOf<Option<Int>>(Some(1), Some(1)).combineAll() }
Monoid.option(Monoid.int()).run { listOf<Option<Int>>(Some(1), Some(1)).combineAll() }
```

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, for example.

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.

```kotlin:ank
import arrow.core.*
import arrow.core.extensions.list.foldable.foldMap
import arrow.core.foldMap
import arrow.core.identity
import arrow.core.int
import arrow.typeclasses.Monoid

listOf(1, 2, 3, 4, 5).k().foldMap(Int.monoid(), ::identity)
listOf(1, 2, 3, 4, 5).foldMap(Monoid.int(), ::identity)
```

```kotlin:ank
listOf(1, 2, 3, 4, 5).k().foldMap(String.monoid(), { it.toString() })
import arrow.core.foldMap
import arrow.core.string
import arrow.typeclasses.Monoid

listOf(1, 2, 3, 4, 5).foldMap(Monoid.string()) { it.toString() }
```

To use this with a function that produces a tuple, we can define a Monoid for a tuple that will be valid for any tuple where the types it contains also have a Monoid available.
To use this with a function that produces a pair, we can define a Monoid for a pair that will be valid for any pair where the types it contains also have a Monoid available.

```kotlin:ank:silent
fun <A, B> monoidTuple(MA: Monoid<A>, MB: Monoid<B>): Monoid<Tuple2<A, B>> =
object: Monoid<Tuple2<A, B>> {
import arrow.typeclasses.Monoid

override fun Tuple2<A, B>.combine(y: Tuple2<A, B>): Tuple2<A, B> {
val (xa, xb) = this
val (ya, yb) = y
return Tuple2(MA.run { xa.combine(ya) }, MB.run { xb.combine(yb) })
}
fun <A, B> monoidPair(MA: Monoid<A>, MB: Monoid<B>): Monoid<Pair<A, B>> =
object : Monoid<Pair<A, B>> {

override fun empty(): Tuple2<A, B> = Tuple2(MA.empty(), MB.empty())
}
override fun Pair<A, B>.combine(other: Pair<A, B>): Pair<A, B> {
val (thisA, thisB) = this
val (otherA, otherB) = other
return Pair(MA.run { thisA.combine(otherA) }, MB.run { thisB.combine(otherB) })
}

override fun empty(): Pair<A, B> = Pair(MA.empty(), MB.empty())
}
```

This way, we are able to combine both values in one pass, hurrah!

```kotlin:ank
val M = monoidTuple(Int.monoid(), String.monoid())
val list = listOf(1, 1).k()
import arrow.core.foldMap
import arrow.core.int
import arrow.core.string
import arrow.typeclasses.Monoid

val M = monoidPair(Monoid.int(), Monoid.string())
val list = listOf(1, 1)

list.foldMap(M) { n: Int ->
Tuple2(n, n.toString())
Pair(n, n.toString())
}
```
39 changes: 23 additions & 16 deletions arrow-site/docs/docs/arrow/typeclasses/semigroup/README.md
Expand Up @@ -31,34 +31,41 @@ For example, `Int` values are combined using addition by default, but multiplica
Now that you've learned about the Semigroup instance for Int, try to guess how it works in the following examples:

```kotlin:ank
import arrow.*
import arrow.core.extensions.*
import arrow.core.int
import arrow.typeclasses.Semigroup

Int.semigroup().run { 1.combine(2) }
Semigroup.int().run { 1.combine(2) }
```

```kotlin:ank
import arrow.core.*
import arrow.core.extensions.*
import arrow.core.extensions.listk.semigroup.*
import arrow.core.list
import arrow.typeclasses.Semigroup

ListK.semigroup<Int>().run {
listOf(1, 2, 3).k().combine(listOf(4, 5, 6).k())
Semigroup.list<Int>().run {
listOf(1, 2, 3).combine(listOf(4, 5, 6))
}
```

```kotlin:ank
import arrow.core.*
import arrow.core.extensions.option.semigroup.semigroup
import arrow.core.Option
import arrow.core.int
import arrow.core.option
import arrow.typeclasses.Semigroup

Option.semigroup(Int.semigroup()).run {
Option(1).combine(Option(2))
Semigroup.option(Semigroup.int()).run {
Option(1).combine(Option(2))
}
```

```kotlin:ank
Option.semigroup(Int.semigroup()).run {
Option(1).combine(None)
import arrow.core.Option
import arrow.core.None
import arrow.core.int
import arrow.core.option
import arrow.typeclasses.Semigroup

Semigroup.option(Semigroup.int()).run {
Option(1).combine(None)
}
```

Expand All @@ -67,8 +74,8 @@ Many of these types have methods defined directly on them, which allow for this
Additionally, `Semigroup` adds `+` syntax to all types for which a Semigroup instance exists:

```kotlin:ank
Option.semigroup(Int.semigroup()).run {
Option(1) + Option(2)
Semigroup.option(Semigroup.int()).run {
Option(1) + Option(2)
}
```

Expand Down