If you have the book Learn You a Haskell for Great Good you get to start a new chapter: “Monoids.” For the website, it’s still Functors, Applicative Functors and Monoids.
First, it seems like Cats is missing newtype
/tagged type facility.
We’ll implement our own later.
Haskell’s Monoid
is split into Semigroup
and Monoid
in Cats. They are also type aliases of algebra.Semigroup
and algebra.Monoid
. As with Apply
and Applicative
, Semigroup
is a weaker version of Monoid
. If you can solve the same problem, weaker is cooler because you’re making fewer assumptions.
LYAHFGG:
It doesn’t matter if we do
(3 * 4) * 5
or3 * (4 * 5)
. Either way, the result is60
. The same goes for++
. …We call this property associativity.
*
is associative, and so is++
, but-
, for example, is not.
Let’s check this:
import cats._, cats.syntax.all._
assert { (3 * 2) * (8 * 5) === 3 * (2 * (8 * 5)) }
assert { List("la") ++ (List("di") ++ List("da")) === (List("la") ++ List("di")) ++ List("da") }
No error means, they are equal.
Here’s the typeclass contract for algebra.Semigroup
.
/**
* A semigroup is any set `A` with an associative operation (`combine`).
*/
trait Semigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable {
/**
* Associative operation taking which combines two values.
*/
def combine(x: A, y: A): A
....
}
This enables combine
operator and its symbolic alias |+|
. Let’s try using this.
List(1, 2, 3) |+| List(4, 5, 6)
// res2: List[Int] = List(1, 2, 3, 4, 5, 6)
"one" |+| "two"
// res3: String = "onetwo"
Associativity is the only law for Semigroup
.
(x |+| y) |+| z = x |+| (y |+| z)
Here’s how we can check the Semigroup laws from the REPL. Review Checking laws with discipline for the details:
scala> import cats._, cats.data._, cats.implicits._
import cats._
import cats.data._
import cats.implicits._
scala> import cats.kernel.laws.GroupLaws
import cats.kernel.laws.GroupLaws
scala> val rs1 = GroupLaws[Int].semigroup(Semigroup[Int])
rs1: cats.kernel.laws.GroupLaws[Int]#GroupProperties = cats.kernel.laws.GroupLaws$GroupProperties@5a077d1d
scala> rs1.all.check
+ semigroup.associativity: OK, passed 100 tests.
+ semigroup.combineN(a, 1) == a: OK, passed 100 tests.
+ semigroup.combineN(a, 2) == a |+| a: OK, passed 100 tests.
+ semigroup.serializable: OK, proved property.
List(1, 2, 3) |+| List(4, 5, 6)
// res4: List[Int] = List(1, 2, 3, 4, 5, 6)
For Int
a semigroup can be formed under both +
and *
.
Instead of tagged types, cats provides only the instance additive.
Trying to use operator syntax here is tricky.
def doSomething[A: Semigroup](a1: A, a2: A): A =
a1 |+| a2
doSomething(3, 5)(Semigroup[Int])
// res5: Int = 8
I might as well stick to function syntax:
Semigroup[Int].combine(3, 5)
// res6: Int = 8