Skip to content

Commit 7e2cd66

Browse files
committed
Avoid implicit objects that extend nothing
An implicit object that only exists as an extension method container is a bit strange. Since extension methods can now also be made visible by imports it makes sense to de-emphasize extension methods in implicit values until we discuss typeclasses later in the section.
1 parent f97c6ff commit 7e2cd66

File tree

2 files changed

+29
-44
lines changed

2 files changed

+29
-44
lines changed

docs/docs/reference/extension-methods.md

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@ Extension methods allow one to add methods to a type after the type is defined.
88
```scala
99
case class Circle(x: Double, y: Double, radius: Double)
1010

11-
implicit object CircleOps {
12-
def (c: Circle) circumference: Double = c.radius * math.Pi * 2
13-
}
11+
def (c: Circle) circumference: Double = c.radius * math.Pi * 2
1412
```
1513

16-
`CircleOps` adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`:
14+
Like regular methods, extension methods can be invoked with infix `.`:
1715

1816
```scala
1917
val circle = Circle(0, 0, 1)
@@ -29,22 +27,31 @@ to the plain method, and can also be invoked as such:
2927
```scala
3028
def circumference(c: Circle): Double = c.radius * math.Pi * 2
3129

32-
assert(circle.circumference == CircleOps.circumference(circle))
30+
assert(circle.circumference == circumference(circle))
3331
```
3432

3533
### Translation of Calls to Extension Methods
3634

37-
When is an extension method considered? There are two possibilities. The first (and recommended one) is by defining the extension method as a member of an implicit value. The method can then be used as an extension method wherever the implicit value is applicable. The second possibility is by making the extension method itself visible under a simple name, typically by importing it. As an example, consider an extension method `longestStrings` on `String`. We can either define it like this:
35+
When is an extension method applicable? There are two possibilities.
36+
37+
- An extension method is applicable if it is visible under a simple name, by being defined
38+
or inherited or imported in a scope enclosing the application.
39+
- An extension method is applicable if it is a member of an eligible implicit value at the point of the application.
3840

41+
As an example, consider an extension method `longestStrings` on `String` defined in a trait `StringSeqOps`.
3942

4043
```scala
41-
implicit object StringSeqOps1 {
44+
trait StringSeqOps {
4245
def (xs: Seq[String]) longestStrings = {
4346
val maxLength = xs.map(_.length).max
4447
xs.filter(_.length == maxLength)
4548
}
4649
}
4750
```
51+
We can make the extension method available by defining an implicit instance of `StringSeqOps`, like this:
52+
```scala
53+
implicit object ops1 extends StringSeqOps
54+
```
4855
Then
4956
```scala
5057
List("here", "is", "a", "list").longestStrings
@@ -53,13 +60,8 @@ is legal everywhere `StringSeqOps1` is available as an implicit value. Alternati
5360
as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method.
5461

5562
```scala
56-
object StringOps2 {
57-
def (xs: Seq[String]) longestStrings = {
58-
val maxLength = xs.map(_.length).max
59-
xs.filter(_.length == maxLength)
60-
}
61-
}
62-
import StringSeqOps2.longestStrings
63+
object ops2 extends StringSeqOps
64+
import ops2.longestStrings
6365
List("here", "is", "a", "list").longestStrings
6466
```
6567
The precise rules for resolving a selection to an extension method are as follows.
@@ -100,30 +102,23 @@ to the implementation of right binding operators as normal methods.
100102

101103
### Generic Extensions
102104

103-
The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible
104-
to extend a generic type by adding type parameters to an extension method:
105+
The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method:
105106

106107
```scala
107-
implicit object ListOps {
108-
def (xs: List[T]) second [T] = xs.tail.head
109-
}
108+
def (xs: List[T]) second [T] = xs.tail.head
110109
```
111110

112111
or:
113112

114113

115114
```scala
116-
implicit object ListListOps {
117-
def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _)
118-
}
115+
def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _)
119116
```
120117

121118
or:
122119

123120
```scala
124-
implicit object NumericOps {
125-
def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y)
126-
}
121+
def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y)
127122
```
128123

129124
As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause.

tests/run/extension-methods.scala

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,29 @@
11
object Test extends App {
22

3-
implicit object O {
4-
def (x: Int) em: Boolean = x > 0
5-
}
3+
def (x: Int) em: Boolean = x > 0
64

7-
assert(1.em == O.em(1))
5+
assert(1.em == em(1))
86

97
case class Circle(x: Double, y: Double, radius: Double)
108

11-
implicit object CircleOps {
12-
def (c: Circle) circumference: Double = c.radius * math.Pi * 2
13-
}
9+
def (c: Circle) circumference: Double = c.radius * math.Pi * 2
1410

1511
val circle = new Circle(1, 1, 2.0)
1612

17-
assert(circle.circumference == CircleOps.circumference(circle))
13+
assert(circle.circumference == circumference(circle))
1814

19-
implicit object StringOps {
20-
def (xs: Seq[String]) longestStrings: Seq[String] = {
21-
val maxLength = xs.map(_.length).max
22-
xs.filter(_.length == maxLength)
23-
}
15+
def (xs: Seq[String]) longestStrings: Seq[String] = {
16+
val maxLength = xs.map(_.length).max
17+
xs.filter(_.length == maxLength)
2418
}
2519
val names = List("hi", "hello", "world")
2620
assert(names.longestStrings == List("hello", "world"))
2721

28-
implicit object SeqOps {
29-
def (xs: Seq[T]) second[T] = xs.tail.head
30-
}
22+
def (xs: Seq[T]) second[T] = xs.tail.head
3123

3224
assert(names.longestStrings.second == "world")
3325

34-
implicit object ListListOps {
35-
def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _)
36-
}
26+
def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _)
3727

3828
assert(List(names, List("!")).flattened == names :+ "!")
3929
assert(Nil.flattened == Nil)

0 commit comments

Comments
 (0)