Skip to content

Commit a98b901

Browse files
author
Aggelos Biboudis
authored
Merge pull request #5774 from LPTK/doc
Documentation on the "companion object extension" type class pattern
2 parents 7c9401f + 9739081 commit a98b901

File tree

1 file changed

+37
-10
lines changed

1 file changed

+37
-10
lines changed

docs/docs/reference/other-new-features/extension-methods.md

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,38 +168,65 @@ that problem, it is recommended that the name of an extension method is
168168
preceded by a space and is also followed by a space if there are more parameters
169169
to come.
170170

171-
### Extension Methods and TypeClasses
171+
### Extension Methods and Type Classes
172172

173-
The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`.
173+
The rules for expanding extension methods make sure that they work seamlessly with type classes. For instance, consider `SemiGroup` and `Monoid`.
174174
```scala
175-
// Two typeclasses:
175+
// Two type classes:
176176
trait SemiGroup[T] {
177177
def (x: T) combine(y: T): T
178178
}
179179
trait Monoid[T] extends SemiGroup[T] {
180180
def unit: T
181181
}
182-
183-
// An instance declaration:
184-
implicit object StringMonoid extends Monoid[String] {
185-
def (x: String) combine (y: String): String = x.concat(y)
186-
def unit: String = ""
182+
object Monoid {
183+
// An instance declaration:
184+
implicit object StringMonoid extends Monoid[String] {
185+
def (x: String) combine (y: String): String = x.concat(y)
186+
def unit: String = ""
187+
}
187188
}
188189

189190
// Abstracting over a typeclass with a context bound:
190191
def sum[T: Monoid](xs: List[T]): T =
191192
xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_))
192193
```
194+
193195
In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`,
194196
which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit
195197
evidence parameter summoned by the context bound `[T: Monoid]`. This works since
196198
extension methods apply everywhere their enclosing object is available as an implicit.
197199

200+
To avoid having to write `implicitly[Monoid[T]].unit` to access the `unit` method in `Monoid[T]`,
201+
we can make `unit` itself an extension method on the `Monoid` _companion object_,
202+
as shown below:
203+
204+
```scala
205+
trait Monoid[T] extends SemiGroup[T] {
206+
def (self: Monoid.type) unit: T
207+
}
208+
object Monoid {
209+
implicit object StringMonoid extends Monoid[String] {
210+
def (x: String) combine (y: String): String = x.concat(y)
211+
def (self: Monoid.type) unit: String = ""
212+
}
213+
}
214+
```
215+
216+
This allows us to write `Monoid.unit` instead of `implicitly[Monoid[T]].unit`,
217+
letting the expected type distinguish which instance we want to use:
218+
219+
```scala
220+
def sum[T: Monoid](xs: List[T]): T =
221+
xs.foldLeft(Monoid.unit)(_.combine(_))
222+
```
223+
224+
198225
### Generic Extension Classes
199226

200227
As another example, consider implementations of an `Ord` type class with a `minimum` value:
201228
```scala
202-
trait Ord[T]
229+
trait Ord[T] {
203230
def (x: T) compareTo (y: T): Int
204231
def (x: T) < (y: T) = x.compareTo(y) < 0
205232
def (x: T) > (y: T) = x.compareTo(y) > 0
@@ -213,7 +240,7 @@ As another example, consider implementations of an `Ord` type class with a `mini
213240
}
214241

215242
class ListOrd[T: Ord] extends Ord[List[T]] {
216-
def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match
243+
def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match {
217244
case (Nil, Nil) => 0
218245
case (Nil, _) => -1
219246
case (_, Nil) => +1

0 commit comments

Comments
 (0)