From f9a7629a419243fadb92f8b856f236e066fc5d5a Mon Sep 17 00:00:00 2001 From: Quentin Bernet Date: Wed, 8 Dec 2021 14:45:43 +0100 Subject: [PATCH 1/4] Add explanation for polymorphic eta-expansion --- spec/06-expressions.md | 44 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/spec/06-expressions.md b/spec/06-expressions.md index 49687a2bf97e..180dfd01f742 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -1775,8 +1775,10 @@ so `scala.Any` is the type inferred for `a`. ### Eta Expansion -_Eta-expansion_ converts an expression of method type to an -equivalent expression of function type. It proceeds in two steps. +_Eta-expansion_ converts an expression of (potentially polymorphic) method +type to an equivalent expression of (potentially polymorphic) function type. + +If the method is not polymorphic, it proceeds in two steps: First, one identifies the maximal sub-expressions of ´e´; let's say these are ´e_1 , \ldots , e_m´. For each of these, one creates a @@ -1784,7 +1786,7 @@ fresh name ´x_i´. Let ´e'´ be the expression resulting from replacing every maximal subexpression ´e_i´ in ´e´ by the corresponding fresh name ´x_i´. Second, one creates a fresh name ´y_i´ for every argument type ´T_i´ of the method (´i = 1 , \ldots , -n´). The result of eta-conversion is then: +n´). The result of eta-expansion is then: ```scala { val ´x_1´ = ´e_1´; @@ -1794,6 +1796,42 @@ n´). The result of eta-conversion is then: } ``` +If the method is polymorphic, it proceeds as follows: + +If the expected type is also polymorphic and the type clauses are compatible, +a polymorphic function is created, taking fresh type arguments, and apllying +them to the method. The bounds are those of the expected type if present +(compatibility ensures they are strict enough). If there is no expected type, +the bounds are the method's bounds. The result of eta-expansion is then: + +```scala +[´Fresh_1 , \ldots , Fresh_n´] => ´e´[´Fresh_1 , \ldots , Fresh_n´] +``` + +Note that the compatibility of the subexpression and the expected +subexpression will again be checked, potentially producing more +eta-expansions, for example the method `def ident[T](x: T): T` will get +eta-expanded to: +```scala +[T] => (x: T) => ident[T](x) +``` + + +In the case where the expected type is not polymorphic, or the clauses are +incompatible then fresh type variables are inserted, they will either be +replaced by a concrete type or throw a typing error, for example with our +previous `ident`: + +```scala +val identInt: Int => Int = ident +``` + +Will get expanded to: + +```scala +val identInt: Int => Int = (x: Int) => ident[Int](x) +``` + The behavior of [call-by-name parameters](#function-applications) is preserved under eta-expansion: the corresponding actual argument expression, a sub-expression of parameterless method type, is not evaluated in the expanded block. From d2fb919d17083586c216649f06190ab05ebc2361 Mon Sep 17 00:00:00 2001 From: Quentin Bernet Date: Wed, 8 Dec 2021 15:19:16 +0100 Subject: [PATCH 2/4] Add type bounds in example --- spec/06-expressions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/06-expressions.md b/spec/06-expressions.md index 180dfd01f742..ac1fbb7054e6 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -1805,7 +1805,7 @@ them to the method. The bounds are those of the expected type if present the bounds are the method's bounds. The result of eta-expansion is then: ```scala -[´Fresh_1 , \ldots , Fresh_n´] => ´e´[´Fresh_1 , \ldots , Fresh_n´] +[´T_1 <: U_1 >: L_1, \ldots , T_n <: U_n >: L_n´] => ´e´[´T_1 , \ldots , T_n´] ``` Note that the compatibility of the subexpression and the expected From 63c17902d74a295a059058cc9bd8a5b2160e83e8 Mon Sep 17 00:00:00 2001 From: Quentin Bernet Date: Wed, 8 Dec 2021 15:24:05 +0100 Subject: [PATCH 3/4] Fix punctuation --- spec/06-expressions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/06-expressions.md b/spec/06-expressions.md index ac1fbb7054e6..8584ab1336cc 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -1818,8 +1818,8 @@ eta-expanded to: In the case where the expected type is not polymorphic, or the clauses are -incompatible then fresh type variables are inserted, they will either be -replaced by a concrete type or throw a typing error, for example with our +incompatible, then fresh type variables are inserted. They will either be +replaced by a concrete type or throw a typing error. For example with our previous `ident`: ```scala From e5f5853e617b18646a4229e9acfc1de72f36ed49 Mon Sep 17 00:00:00 2001 From: Quentin Bernet Date: Tue, 21 Dec 2021 12:47:15 +0100 Subject: [PATCH 4/4] Add links and dividing subsections --- spec/06-expressions.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/spec/06-expressions.md b/spec/06-expressions.md index 8584ab1336cc..942333098a66 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -1778,7 +1778,14 @@ so `scala.Any` is the type inferred for `a`. _Eta-expansion_ converts an expression of (potentially polymorphic) method type to an equivalent expression of (potentially polymorphic) function type. -If the method is not polymorphic, it proceeds in two steps: +The behavior of [call-by-name parameters](#function-applications) +is preserved under eta-expansion: the corresponding actual argument expression, +a sub-expression of parameterless method type, is not evaluated in the expanded block. + +The following are the three distinct cases of eta-expansion: + + +#### Non-polymorphic method First, one identifies the maximal sub-expressions of ´e´; let's say these are ´e_1 , \ldots , e_m´. For each of these, one creates a @@ -1796,29 +1803,30 @@ n´). The result of eta-expansion is then: } ``` -If the method is polymorphic, it proceeds as follows: +#### Polymorphic method & compatible polymorphic function -If the expected type is also polymorphic and the type clauses are compatible, -a polymorphic function is created, taking fresh type arguments, and apllying -them to the method. The bounds are those of the expected type if present -(compatibility ensures they are strict enough). If there is no expected type, -the bounds are the method's bounds. The result of eta-expansion is then: +Let ´e´ be a method `´m´` taking `[´T_1 <: U_1 >: L_1, \ldots , T_n <: U_n >: L_n´]` +as argument, +if the expected type is `[´T'_1 <: U'_1 >: L'_1, \ldots , T'_n <: U'_n >: L'_n´] => ´T'_{ret}´`, +and each `´T'_x´` [conforms](03-types.html#conformance) to `´T_x´`, +or if there's no expected type; `´T'_x´ := ´T_x´`, then the result of eta-expansion is: ```scala -[´T_1 <: U_1 >: L_1, \ldots , T_n <: U_n >: L_n´] => ´e´[´T_1 , \ldots , T_n´] +[´T'_1 <: U'_1 >: L'_1, \ldots , T'_n <: U'_n >: L'_n´] => ´m´[´T'_1, \ldots , T'_n´] ``` -Note that the compatibility of the subexpression and the expected -subexpression will again be checked, potentially producing more +Note that the [compatibility](03-types.html#compatibility) of the type of `´m´[´T'_1, \ldots , T'_n´]` + and `´T'_{ret}´` will again be checked, potentially producing more eta-expansions, for example the method `def ident[T](x: T): T` will get eta-expanded to: ```scala [T] => (x: T) => ident[T](x) ``` +#### Remaining cases -In the case where the expected type is not polymorphic, or the clauses are -incompatible, then fresh type variables are inserted. They will either be +In the case where the expected type is not polymorphic, or the type parameter +don't conform, then fresh type variables are applied. They will either be replaced by a concrete type or throw a typing error. For example with our previous `ident`: @@ -1832,10 +1840,6 @@ Will get expanded to: val identInt: Int => Int = (x: Int) => ident[Int](x) ``` -The behavior of [call-by-name parameters](#function-applications) -is preserved under eta-expansion: the corresponding actual argument expression, -a sub-expression of parameterless method type, is not evaluated in the expanded block. - ### Dynamic Member Selection The standard Scala library defines a marker trait `scala.Dynamic`. Subclasses of this trait are able to intercept selections and applications on their instances by defining methods of the names `applyDynamic`, `applyDynamicNamed`, `selectDynamic`, and `updateDynamic`.