slides can look grainy and/or out of focus when seen on slideshare - download for flawless quality - As a pretext for learning the basics of some new Scala 3 features, we take two very simple Semigroup and Monoid typeclasses and make them a bit better by migrating them to Scala 3.
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Scala 3 by Example - better Semigroup and Monoid
1. Scala 3 by example
better Semigroup and Monoid
as a pretext for learning the basics of some new Scala 3 features
we take two very simple Semigroup and Monoid typeclasses and make them a bit better
by migrating them to Scala 3
@philip_schwarzslides by
https://www.slideshare.net/pjschwarz
2. Here is the code we are going to play around with in this slide
deck. It is not production code. It was put together purely as a
simple playground for experimenting with some Scala 3 features.
trait Semigroup[A] {
def combine(l: A, r: A): A
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(combine(_,_))
def combineOption(as: A*): Option[A] =
combineAllOption(as)
}
implicit val intSemigroup = new Semigroup[Int] {
def combine(l: Int, r: Int): Int = l + r
}
implicit val stringSemigroup = new Semigroup[String] {
def combine(l: String, r: String): String = l + r
}
object Semigroup {
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
}
object Syntax {
implicit class SemigroupSyntax[A](a: A)(implicit semigroup: Semigroup[A]) {
def |+|(other: A): A = semigroup.combine(a, other)
}
}
import Syntax._
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
@philip_schwarz
3. trait Semigroup[A] {
def combine(l: A, r: A): A
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(combine(_,_))
def combineOption(as: A*): Option[A] =
combineAllOption(as)
}
implicit val intSemigroup = new Semigroup[Int] {
def combine(l: Int, r: Int): Int = l + r
}
implicit val stringSemigroup = new Semigroup[String] {
def combine(l: String, r: String): String = l + r
}
object Semigroup {
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
}
object Syntax {
implicit class SemigroupSyntax[A](a: A)(implicit semigroup: Semigroup[A]) {
def |+|(other: A): A = semigroup.combine(a, other)
}
}
import Syntax._
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
The first thing we are going to do is
1. switch the compiler from Scalac to Dotty
2. introduce a main method using the new @main annotation
3. replace all curly brace pairs with the with keyword
From https://dotty.epfl.ch/docs/reference/changed-features/main-functions.html
Main Methods
Scala 3 offers a new way to define programs that can be invoked from the
command line: A @main annotation on a method turns this method into an
executable program.
From https://dotty.epfl.ch/docs/reference/other-new-features/indentation-
new.html
Optional Braces
As an experimental feature, Scala 3 enforces some rules on indentation and
allows some occurrences of braces {...} to be optional.
• First, some badly indented programs are ruled out, which means they are
flagged with warnings.
• Second, some occurrences of braces {...} are made optional. Generally, the
rule is that adding a pair of optional braces will not change the meaning of a
well-indented program.
…
New Role of With
To make braces optional for constructs like class bodies, the syntax of the
language is changed so that a class body or similar construct may optionally be
prefixed with with.
…
7. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(combine(_,_))
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
object Syntax with
implicit class SemigroupSyntax[A](a: A)(implicit semigroup: Semigroup[A]) with
def |+|(other: A): A = semigroup.combine(a, other)
import Syntax._
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
The current version of the code uses an implicit SemigroupSyntax
class to implement extension methods so as to provide |+|, the Tie
Fighter operator, as an infix alias for combine.
What we are going to do next is use Scala 3’s extension method
feature, which relaces implicit classes with a clearer and simpler
mechanism.
“You may occasionally see
extension methods referred
to as “type enrichment” or
“pimping”. These are older
terms that we don’t use
anymore.”
9. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(combine(_,_))
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
object Syntax with
implicit class SemigroupSyntax[A](a: A)(implicit semigroup: Semigroup[A]) with
def |+|(other: A): A = semigroup.combine(a, other)
import Syntax._
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
before the changes after the changes
@main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
10. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
latest
11. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
@philip_schwarz
The way the combineAllOption function of Semigroup is
accessed in the current version of the code is via a Semigroup
implicit instance that is summoned by calling Semigroup’s apply
function, e.g.
Semigroup[Int].combineAllOption( List(2,3,4) )
What we are going to do next is make combineAllOption an
extension method, so that it can be invoked directly on anything
for which there is an implicit Semigroup instance.
13. before the changes after the changes
@main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
@main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( List().combineAllOption == None)
assert( List(2,3,4).combineAllOption == Some(9))
assert( List("2","3","4").combineAllOption == Some("234"))
14. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( List().combineAllOption == None)
assert( List(2,3,4).combineAllOption == Some(9))
assert( List("2","3","4").combineAllOption == Some("234"))
latest
15. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
implicit val intSemigroup = new Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
implicit val stringSemigroup = new Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
The current version of
the code uses implicits
to implement the
Semigroup typeclass.
So what we are going
to do next is switch
from implicits to
givens.
21. before the changes after the changes
@main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
given Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
given Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A](given semigroup: Semigroup[A]) = semigroup
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( List().combineAllOption == None)
assert( List(2,3,4).combineAllOption == Some(9))
assert( List("2","3","4").combineAllOption == Some("234"))
@main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
given Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
given Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A]((given Semigroup[A]) = summon[Semigroup[A]]
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( List().combineAllOption == None)
assert( List(2,3,4).combineAllOption == Some(9))
assert( List("2","3","4").combineAllOption == Some("234"))
22. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
given Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
given Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A]((given Semigroup[A]) = summon[Semigroup[A]]
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( List().combineAllOption == None)
assert( List(2,3,4).combineAllOption == Some(9))
assert( List("2","3","4").combineAllOption == Some("234"))
latest
23. @main def main =
trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
given Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
given Semigroup[String] with
def combine(l: String, r: String): String = l + r
object Semigroup with
def apply[A]((given Semigroup[A]) = summon[Semigroup[A]]
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( List().combineAllOption == None)
assert( List(2,3,4).combineAllOption == Some(9))
assert( List("2","3","4").combineAllOption == Some("234"))
trait Semigroup[A] {
def combine(l: A, r: A): A
def combineAllOption(as: Seq[A]): Option[A] =
as.reduceOption(combine(_,_))
def combineOption(as: A*): Option[A] =
combineAllOption(as)
}
implicit val intSemigroup = new Semigroup[Int] {
def combine(l: Int, r: Int): Int = l + r
}
implicit val stringSemigroup = new Semigroup[String] {
def combine(l: String, r: String): String = l + r
}
object Semigroup {
def apply[A](implicit semigroup: Semigroup[A]) = semigroup
}
object Syntax {
implicit class SemigroupSyntax[A](a: A)(implicit semigroup: Semigroup[A]) {
def |+|(other: A): A = semigroup.combine(a, other)
}
}
import Syntax._
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( Semigroup[String].combineAllOption( List() ) == None)
assert( Semigroup[Int].combineAllOption( List(2,3,4) ) == Some(9))
assert( Semigroup[String].combineAllOption( List("2","3","4") ) == Some("234"))
@philip_schwarz
Here is the original code and next to it the
version with all the changes we have made.
24. In the next slide we’ll see Martin Odersky introduce
givens. In the process, we’ll briefly see an aspect of givens
that we have not used here, i.e. conditional givens.
25. Given is the new implicit.
So, given instances. Here is a first introductory sample, it’s the one that we mostly start with, when we do typeclasses, we want to explain what orderings are for Ints and Lists of T,
so here is a trait, Ord, it has three methods, compare, less than and greater than. So that’s essentially another new thing in Scala 3, we can write extension methods like that, we just
say this method is applied infix, and that’s the left argument and that is the right argument. OK, and now we say well we want to have two instances for ordering. Integers are ordered
and Lists are ordered. In both of these cases we have to explain what compare is because compare is an abstract method here and the other two are already implemented.
So for intOrd we have this usual compare that you see here, and for listOrd we have a slightly longer compare that I have left out, but the important part for the listOrd is that it
works for any T, for which there is an Ord[T], of course, that’s the conditional implicit and then if you have an Ord[T] you can construct an Ord[List[T]] and that is what its
compare method is.
So you see that whether it is conditional or not, previously it was like an implicit object or an implicit def or an implicit class and all these things had to be constructed in a very subtle
way, now it is just given, you say you have a given instance for a given type and then we explain essentially what is missing and that’s all.
Lambda World 2019
Implicits Revisited – Martin Odersky
26. The other new thing is that the names of these given instances can also be left out. So
you can also write it like that. You can just say, there is a given ordering of Int where that
is the compare method and here you say for any T that itself has an ordering there is a
given ordering of List[T] where the compare method is this, because the names of
these things after all they don’t matter because you will usually not refer to these things.
The point of givens or implicits is that the compiler will produce them for you, so why
should you bother giving them a name? There could be reasons for giving them a name of
course, the main reason is binary stability, you don’t want to rely on the compiler inferring
names and maybe inferring different names in different versions, that way you can
essentially nail down a name as a programmer, so for long-living APIs that actually makes
a lot of sense, but for doing things quickly you should be able to leave these things out.
Lambda World 2019
Implicits Revisited
Martin Odersky
By the way, on the right hand side,
the conditional given for
Ord[Int] has been replaced with
a context bound for Ord[Int].
27. Lambda World 2019 - Implicits Revisited - Martin Odersky
So you see it is actually a very quiet and nice syntax for typeclasses if you compare to what you have to do now, it is much much nicer, in particular in
the way it treats infix methods. So far, to do this in any way that is reasonable you needed essentially a macro package called Simulacrum which would
essentially add a bunch of imports to your program to make it work in a way but now it is available much more directly and much more robustly.
see also https://dotty.epfl.ch/docs/reference/contextual/typeclasses.html
28. This summon method that I have
shown here, that is essentially
the new implicitly.
Martin Odersky
30. trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
Our existing Semigroup code,
for reference.
trait Monoid[A] extends Semigroup[A] with
def unit: A
object Monoid with
def apply[A](given Monoid[A]) = summon[Monoid[A]]
given Monoid[Int] with
def combine(l: Int, r: Int): Int = l + r
def unit: Int = 0
given Monoid[String] with
def combine(l: String, r: String): String = l + r
def unit: String = ""
assert( (2 |+| 3 |+| Monoid[Int].unit) == 5 )
assert( ("2" |+| "3" |+| Monoid[String].unit) == "23" )
def combineAll[A: Monoid](as: Seq[A]): A =
as.foldLeft(summon[Monoid[A]].unit)(_ |+| _)
assert( combineAll( List(2,3,4) ) == 9 )
assert( combineAll( List("2","3","4") ) == "234" )
def combineAll[A: Monoid](as: Seq[A]): A =
as.foldLeft(Monoid[A].unit)(_ |+| _)
def combineAll[A](as: Seq[A])(given monoid: Monoid[A]): A =
as.foldLeft(monoid.unit)(_ |+| _)
Let’s define a Monoid, a convenient way of
summoning a Monoid, and two Monoid instances.
As for the sum method shown earlier by Martin Odersky, we
could do something similar and call it combineAll, to be
consistent with what we did earlier for Semigroup.
And by the way, here is how the combineAll function would
look like if we hadn’t used a context bound.
But it is less verbose using the Monoid apply function we
have just introduced.
31. trait Semigroup[A] with
def combine(l: A, r: A): A
def (l: A) |+| (r: A): A = combine(l, r)
def (as: Seq[A]) combineAllOption: Option[A] =
as.reduceOption(_ |+| _)
def combineOption(as: A*): Option[A] =
combineAllOption(as)
object Semigroup with
def apply[A]((given Semigroup[A]) = summon[Semigroup[A]]
given Semigroup[Int] with
def combine(l: Int, r: Int): Int = l + r
given Semigroup[String] with
def combine(l: String, r: String): String = l + r
assert( (2 |+| 3) == 5 )
assert( ("2" |+| "3") == "23")
assert( Semigroup[Int].combineOption() == None)
assert( Semigroup[Int].combineOption(2,3,4) == Some(9))
assert( Semigroup[String].combineOption("2","3","4") == Some("234"))
assert( List().combineAllOption == None)
assert( List(2,3,4).combineAllOption == Some(9))
assert( List("2","3","4").combineAllOption == Some("234"))
trait Monoid[A] extends Semigroup[A] with
def unit: A
def (as: Seq[A]) combineAll: A =
as.foldLeft(unit)(_ |+| _)
def combine(as: A*): A =
as.combineAll
object Monoid with
def apply[A](given Monoid[A]) = summon[Monoid[A]]
given Monoid[Int] with
def combine(l: Int, r: Int): Int = l + r
def unit: Int = 0
given Monoid[String] with
def combine(l: String, r: String): String = l + r
def unit: String = ""
assert( (2 |+| 3 |+| Monoid[Int].unit) == 5 )
assert( ("2" |+| "3" |+| Monoid[String].unit) == "23" )
assert( Monoid[Int].combine() == Monoid[Int].unit)
assert( Monoid[Int].combine(2,3,4) == 9)
assert( Monoid[String].combine("2","3","4") == "234")
assert( List().combineAll == Monoid[Int].unit)
assert( List(2,3,4).combineAll == 9)
assert( List("2","3","4").combineAll == "234")
FWIW, in this final slide, just to be even more consistent with what we did earlier for Semigroup, let’s move
the combineAll function to the Monoid typeclass, so it is analogous to the combineAllOption function in the
Semigroup typeclass and let’s also add to Monoid a combine function that is analogous to the
combineOption function in the Semigroup typeclass.