My previous article was about implicit conversions and extension methods. In this article, we'll discuss the new way to declare typeclasses in Scala 3.
Having learned how to add external methods to arbitrary classes, we want to go even deeper, namely to learn how to convert arbitrary classes to "external" interfaces, that is, without directly inheriting from them. This task is solved by typeclasses.
But first, let's figure out what a typeclass is. Like the concept itself, the term " type class" originated in Haskell. The word "class" is used here not in the narrow sense that is accepted in OOP, but in a broader sense - as a designation of a set of entities that have something in common. (I understand that most people who will read this article have an OOP background, and for them the term "typeclass" sounds something like "oil of oils", although it means "category of oils". To avoid confusion with conventional OOP -classes, instead of "type class" I will use simply transliteration "typeclass" - approx. transl.)
The syntax of the examples is up to dateScala 3.0.0-M3
.
, , , . Scala 3:
// Adapted from this Dotty documentation:
// https://dotty.epfl.ch/docs/reference/contextual/type-classes.html
trait Semigroup[T]:
extension (t: T)
def combine(other: T): T
def <+>(other: T): T = t.combine(other)
trait Monoid[T] extends Semigroup[T]:
def unit: T
, <+>
. — , , 0 — . , Semigroup
Monoid
.
Semigroup
T
extension- combine
<+>
, combine
. unit
Monoid
, extension-. , unit
T
, , , T
, .
:
given StringMonoid: Monoid[String] with
def unit: String = ""
extension (s: String) def combine(other: String): String = s + other
given IntMonoid: Monoid[Int] with
def unit: Int = 0
extension (i: Int) def combine(other: Int): Int = i + other
. , given foo: Bar
— implicit-. Scala3 REPL, , : StringMonoid
IntMonoid
.
- :
"2" <+> ("3" <+> "4") // "234"
("2" <+> "3") <+> "4" // "234"
StringMonoid.unit <+> "2" // "2"
"2" <+> StringMonoid.unit // "2"
2 <+> (3 <+> 4) // 9
(2 <+> 3) <+> 4 // 9
IntMonoid.unit <+> 2 // 2
2 <+> IntMonoid.unit // 2
StringMonoid
IntMonoid
unit
. <+>
extension-, String
Int
. <+>
, .
: given Monoid[String] with ...
. unit
summon[Monoid[String]]
. summon
— implicitly
, implicit- . given_Monoid_String
, , .
, , - ( unit
). .
, . , , IntMonoid
Numeric[T]
:
given NumericMonoid[T](using num: Numeric[T]): Monoid[T] with
def unit: T = num.zero
extension (t: T) def combine(other: T): T = num.plus(t, other)
2.2 <+> (3.3 <+> 4.4) // 9.9
(2.2 <+> 3.3) <+> 4.4 // 9.9
BigDecimal(3.14) <+> NumericMonoid.unit
NumericMonoid[BigDecimal].unit <+> BigDecimal(3.14)
using
, Scala 2 implicit
. .
. NumericMonoid
— , Monoid[T]
— . T
, . NumericMonoid[BigDecimal]
, NumericMonoid
BigDecimal
. num
— NumericMonoid
, using
.
, unit
. -, <+>
. Scala obj1.method(obj2)
.
?
using
.