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

Revamp Semigroup doc #1442

Merged
merged 2 commits into from
Nov 16, 2016
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 114 additions & 46 deletions docs/src/main/tut/typeclasses/monoid.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,70 +7,138 @@ scaladoc: "#cats.kernel.Monoid"
---
# Monoid

`Monoid` extends the [`Semigroup`](semigroup.html) 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 power of `Semigroup` by providing an additional `empty` value.
Copy link
Contributor

Choose a reason for hiding this comment

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

This reads much more cleanly. 👍


```scala
(combine(x, empty) == combine(empty, x) == x)
```

For example, if we have a `Monoid[String]` with `combine` defined as string
concatenation, then `empty = ""`.
```tut:book:silent
trait Semigroup[A] {
def combine(x: A, y: A): A
}

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.
trait Monoid[A] extends Semigroup[A] {
def empty: A
}
```

First some imports.
This `empty` value should be an identity for the `combine` operation, which means the following equalities hold
for any choice of `x`.

```tut:silent
import cats._
import cats.implicits._
```
combine(x, empty) = combine(empty, x) = x
```

Examples.
Many types that form a `Semigroup` also form a `Monoid`, such as `Int`s (with `0`) and `Strings` (with `""`).
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably tangential to this PR, but I've read discussions on the variations of

T:

  • has a
  • is a
  • forms a

Typeclass[T], maybe this is something nice to have a sentence about? I don't care which phrase is used though (maybe a note that they're saying the same thing), and I'm guessing somewhere on http://typelevel.org/cats/typeclasses.html would be appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I ran into this same issue too. I'll keep that in mind on my next pass through (or you can make a PR! :D ) Thanks Long!

Copy link
Contributor

Choose a reason for hiding this comment

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

@adelbertc 👍


```tut:book
Monoid[String].empty
Monoid[String].combineAll(List("a", "b", "c"))
Monoid[String].combineAll(List())
```tut:reset:book:silent
import cats.Monoid

implicit val intAdditionMonoid: Monoid[Int] = new Monoid[Int] {
def empty: Int = 0
def combine(x: Int, y: Int): Int = x + y
}

val x = 1
```

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.

```tut:book
Monoid[Map[String,Int]].combineAll(List(Map("a" -> 1, "b" -> 2), Map("a" -> 3)))
Monoid[Map[String,Int]].combineAll(List())
Monoid[Int].combine(x, Monoid[Int].empty)

Monoid[Int].combine(Monoid[Int].empty, x)
```

# Example usage: Collapsing a list

In the `Semigroup` section we had trouble writing a generic `combineAll` function because we had nothing
to give if the list was empty. With `Monoid` we can return `empty`, giving us

```tut:book:silent
def combineAll[A: Monoid](as: List[A]): A =
as.foldLeft(Monoid[A].empty)(Monoid[A].combine)
```

This is also true if we define our own instances. As an example, let's use
[`Foldable`](foldable.html)'s `foldMap`, which maps over values accumulating
the results, using the available `Monoid` for the type mapped onto.
which can be used for any type that has a `Monoid` instance.

```tut:book:silent
import cats.instances.all._
```

```tut:book
val l = List(1, 2, 3, 4, 5)
l.foldMap(identity)
l.foldMap(i => i.toString)
combineAll(List(1, 2, 3))

combineAll(List("hello", " ", "world"))

combineAll(List(Map('a' -> 1), Map('a' -> 2, 'b' -> 3), Map('b' -> 4, 'c' -> 5)))

combineAll(List(Set(1, 2), Set(2, 3, 4, 5)))
```

To use this
with a function that produces a tuple, cats also provides a `Monoid` for a tuple
that will be valid for any tuple where the types it contains also have a
`Monoid` available, thus.
This function is provided in Cats as `Monoid.combineAll`.

# The `Option` monoid

There are some types that can form a `Semigroup` but not a `Monoid`. For example, the
following `NonEmptyList` type forms a semigroup through `++`, but has no corresponding
identity element to form a monoid.

```tut:book:silent
import cats.Semigroup

final case class NonEmptyList[A](head: A, tail: List[A]) {
def ++(other: NonEmptyList[A]): NonEmptyList[A] = NonEmptyList(head, tail ++ other.toList)

def toList: List[A] = head :: tail
}

object NonEmptyList {
implicit def nonEmptyListSemigroup[A]: Semigroup[NonEmptyList[A]] =
new Semigroup[NonEmptyList[A]] {
def combine(x: NonEmptyList[A], y: NonEmptyList[A]): NonEmptyList[A] = x ++ y
}
}
```

How then can we collapse a `List[NonEmptyList[A]]` ? For such types that only have a `Semigroup` we can
lift into `Option` to get a `Monoid`.

```tut:book:silent
import cats.syntax.semigroup._

implicit def optionMonoid[A: Semigroup]: Monoid[Option[A]] = new Monoid[Option[A]] {
def empty: Option[A] = None

def combine(x: Option[A], y: Option[A]): Option[A] =
x match {
case None => y
case Some(xv) =>
y match {
case None => x
case Some(yv) => Some(xv |+| yv)
}
}
}
```

This is the `Monoid` for `Option`: for any `Semigroup[A]`, there is a `Monoid[Option[A]]`.

Thus:

```tut:reset:book:silent
import cats.Monoid
import cats.data.NonEmptyList
import cats.instances.option._

val list = List(NonEmptyList(1, List(2, 3)), NonEmptyList(4, List(5, 6)))
val lifted = list.map(nel => Option(nel))
```

```tut:book
l.foldMap(i => (i, i.toString)) // do both of the above in one pass, hurrah!
Monoid.combineAll(lifted)
```

-------------------------------------------------------------------------------

This lifting and combining of `Semigroup`s into `Option` is provided by Cats as `Semigroup.combineAllOption`.

-----

N.B.
Cats defines the `Monoid` type class in cats-kernel. The [`cats` package object](https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/package.scala)
defines type aliases to the `Monoid` from cats-kernel, so that you can
`import cats.Monoid`. Also the `Monoid` instance for tuple is also [implemented in cats-kernel](https://github.com/typelevel/cats/blob/master/project/KernelBoiler.scala),
cats merely provides it through [inheritance](https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/std/tuple.scala).
Cats defines the `Monoid` type class in cats-kernel. The
[`cats` package object](https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/package.scala)
defines type aliases to the `Monoid` from cats-kernel, so that you can simply import `cats.Monoid`.
Loading