Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scala 3 by Example - better Semigroup and Monoid

Scala 3 by Example - better Semigroup and Monoid

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.

Keywords: dotty, extension methods, givens, implicits, martin odersky, monoid, scala, scala 3, semigroup

Philip Schwarz

January 25, 2020
Tweet

More Decks by Philip Schwarz

Other Decks in Programming

Transcript

  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_schwarz slides 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. …
  4. 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")) @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
  5. 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")) latest
  6. @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.”
  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")) 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"))
  8. @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
  9. @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.
  10. 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"))
  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 (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
  12. @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.
  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 (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")) @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"))
  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) 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")) 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) 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")) Now let’s simplify the apply function of Semigroup by using the new predefined summon function.
  16. 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"))
  17. @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
  18. @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.
  19. 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.
  20. 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
  21. 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].
  22. 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
  23. This summon method that I have shown here, that is

    essentially the new implicitly. Martin Odersky
  24. @philip_schwarz Let’s have a go at using what we have

    just seen to add the Monoid typeclass to our code.
  25. 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.
  26. 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.