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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update typeclasses docs #2313

Merged
merged 4 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 47 additions & 32 deletions arrow-docs/docs/arrow/typeclasses/monoid/README.md
Original file line number Diff line number Diff line change
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>> {

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) })
import arrow.typeclasses.Monoid

fun <A, B> monoidPair(MA: Monoid<A>, MB: Monoid<B>): Monoid<Pair<A, B>> =
object : Monoid<Pair<A, B>> {

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())
}

override fun empty(): Tuple2<A, B> = Tuple2(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-docs/docs/arrow/typeclasses/semigroup/README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package arrow.typeclasses

import arrow.core.Option

interface Semigroup<A> {
/**
* Combine two [A] values.
Expand All @@ -11,7 +9,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 =
Copy link
Member

Choose a reason for hiding this comment

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

👍

b?.let { combine(it) } ?: this

companion object
}
Original file line number Diff line number Diff line change
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