diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md index ea75387106a5..c655d7f05ecb 100644 --- a/docs/docs/reference/contextual/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -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 @@ -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, @@ -94,6 +115,12 @@ x min 3 ``` For alphanumeric extension operators like `min` an `@infix` annotation is implied. + + The three definitions above translate to ```scala def < (x: String)(y: String) = ... @@ -101,13 +128,15 @@ 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 = @@ -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 @@ -241,4 +272,4 @@ extension on ... extension on ... extension { ... extension { ... -``` \ No newline at end of file +```