Skip to content

doc(extension method): revise examples #8508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 6, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 58 additions & 27 deletions docs/docs/reference/contextual/extension-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ circle.circumference

### Translation of Extension Methods

Extension methods are methods that have a parameter clause in front of the defined
identifier. They translate to methods where the leading parameter section is moved
to after the defined identifier. So, the definition of `circumference` above translates
to the plain method, and can also be invoked as such:
Extension methods are methods that have a parameter clause in front of the defined identifier.
They translate to functions where the leading parameter section is turned into the first argument list of the function.
So, the definition of `circumference` above translates to the following function, and can also be invoked as such:

```scala
def circumference(c: Circle): Double = c.radius * math.Pi * 2

Expand All @@ -32,37 +32,58 @@ assert(circle.circumference == circumference(circle))

### Translation of Calls to Extension Methods

When is an extension method applicable? There are two possibilities.
When is an extension method applicable? There are two possibilities:

- An extension method is applicable if it is visible under a simple name, by being defined
1. An extension method is applicable if it is visible under a simple name, by being defined
or inherited or imported in a scope enclosing the application.
- An extension method is applicable if it is a member of some given instance at the point of the application.
2. An extension method is applicable if it is a member of some given instance at the point of the application.

As an example, consider an extension method `longestStrings` on `Seq[String]` defined in a trait `StringSeqOps`.
Here is an example for the first rule:

```scala
trait StringSeqOps {
def (xs: Seq[String]).longestStrings = {
val maxLength = xs.map(_.length).max
xs.filter(_.length == maxLength)
}
trait IntOps {
def (i: Int).isZero: Boolean = i == 0

def (i: Int).safeMod(x: Int): Option[Int] =
// extension method defined in same scope IntOps
if x.isZero then None
else Some(i % x)
}

object IntOpsEx extends IntOps {
def (i: Int).safeDiv(x: Int): Option[Int] =
// extension method brought into scope via inheritance from IntOps
if x.isZero then None
else Some(i % x)
}

trait SafeDiv {
import IntOpsEx._ // brings safeDiv and safeMod into scope

def (i: Int) divide(d: Int) : Option[(Int, Int)] =
// extension methods imported and thus in scope
(i.safeDiv(d), i.safeMod(d)) match {
case (Some(d), Some(r)) => Some((d, r))
case _ => None
}
}
```
We can make the extension method available by defining a given `StringSeqOps` instance, like this:
```scala
given ops1 as StringSeqOps
```
Then

We build up on the above example to outline the second point.
We can make an extension method available by defining a given instance containing it, like this:
```scala
List("here", "is", "a", "list").longestStrings
given ops1 as IntOps // brings safeMod into scope

1.safeMod(2)
```
is legal everywhere `ops1` is available. Alternatively, we can define `longestStrings` as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method.

Then `safeMod` is legal everywhere `ops1` is available. Anonymous givens (and any other form of givens) are supported as well:
```scala
object ops2 extends StringSeqOps
import ops2.longestStrings
List("here", "is", "a", "list").longestStrings
given SafeDiv //brings divide into scope (safeMod and safeDiv are not automatically exported)

1.divide(2)
```

The precise rules for resolving a selection to an extension method are as follows.

Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional,
Expand Down Expand Up @@ -94,20 +115,28 @@ x min 3
```
For alphanumeric extension operators like `min` an `@infix` annotation is implied.

<!--
TODO: what about @alpha for the non alphanumeric operators, should be required according to
http://dotty.epfl.ch/docs/reference/changed-features/operators.html
"Symbolic methods without @alpha annotations are deprecated"
-->

The three definitions above translate to
```scala
def < (x: String)(y: String) = ...
def +: (xs: Seq[Elem])(x: Elem) = ...
def min(x: Number)(y: Number) = ...
```
Note the swap of the two parameters `x` and `xs` when translating
the right-binding operator `+:` to an extension method. This is analogous
the right-associative operator `+:` to an extension method. This is analogous
to the implementation of right binding operators as normal methods.


### Generic Extensions

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. Examples:
The `IntOps` examples extended a non generic type.
It is also possible to extend a specific instance of a generic type (e.g. Seq[String] -- see `stringOps` further below).
Moreover, it is also possible to extend generic types by adding type parameters to an extension method. Examples:

```scala
def [T](xs: List[T]) second =
Expand All @@ -120,10 +149,12 @@ def [T: Numeric](x: T) + (y: T): T =
summon[Numeric[T]].plus(x, y)
```

If an extension method has type parameters, they come immediately after the `def` and are followed by the extended parameter. When calling a generic extension method, any explicitly given type arguments follow the method name. So the `second` method can be instantiated as follows:
If an extension method has type parameters, they come immediately after the `def` and are followed by the extended parameter.
When calling a generic extension method, any explicitly given type arguments follow the method name. So the `second` method can be instantiated as follows:
```scala
List(1, 2, 3).second[Int]
```
(it's only a showcase, the compiler could of course infer the type).

### Extension Instances

Expand Down Expand Up @@ -241,4 +272,4 @@ extension on ...
extension <ident> on ...
extension { ...
extension <ident> { ...
```
```