diff --git a/_overviews/scala3-book/fun-eta-expansion.md b/_overviews/scala3-book/fun-eta-expansion.md index e45f48cc6..0854c0977 100644 --- a/_overviews/scala3-book/fun-eta-expansion.md +++ b/_overviews/scala3-book/fun-eta-expansion.md @@ -1,7 +1,7 @@ --- -title: Eta Expansion +title: Eta-Expansion type: section -description: This page discusses Eta Expansion, the Scala technology that automatically and transparently converts methods into functions. +description: This page discusses Eta-Expansion, the Scala technology that automatically and transparently converts methods into functions. languages: [ru, zh-cn] num: 31 previous-page: fun-function-variables @@ -9,14 +9,14 @@ next-page: fun-hofs --- -When you look at the Scaladoc for the `map` method on Scala collections classes, you see that it’s defined to accept a _function_: +When you look at the Scaladoc for the `map` method on Scala collections classes, you see that it’s defined to accept a _function_ value: {% tabs fun_1 %} {% tab 'Scala 2 and 3' for=fun_1 %} ```scala -def map[B](f: (A) => B): List[B] - ----------- +def map[B](f: A => B): List[B] +// ^^^^^^ function type from `A` to `B` ``` {% endtab %} @@ -26,7 +26,7 @@ Indeed, the Scaladoc clearly states, “`f` is the _function_ to apply to each e But despite that, somehow you can pass a _method_ into `map`, and it still works: {% tabs fun_2 %} -{% tab 'Scala 2 and 3' for=fun_2 %} +{% tab 'Scala 2 and 3' %} ```scala def times10(i: Int) = i * 10 // a method @@ -36,80 +36,96 @@ List(1, 2, 3).map(times10) // List(10,20,30) {% endtab %} {% endtabs %} -Have you ever wondered how this works---how you can pass a _method_ into `map`, which expects a _function_? - -The technology behind this is known as _Eta Expansion_. +Why does this work? The process behind this is known as _eta-expansion_. It converts an expression of _method type_ to an equivalent expression of _function type_, and it does so seamlessly and quietly. ## The differences between methods and functions -{% comment %} -NOTE: I got the following “method” definition from this page (https://dotty.epfl.ch/docs/reference/changed-features/eta-expansion-spec.html), but I’m not sure it’s 100% accurate now that methods can exist outside of classes/traits/objects. -I’ve made a few changes to that description that I hope are more accurate and up to date. -{% endcomment %} - -Historically, _methods_ have been a part of the definition of a class, although in Scala 3 you can now have methods outside of classes, such as [Toplevel definitions][toplevel] and [extension methods][extension]. +The key difference between methods and functions is that _a function is an object_, i.e. it is an instance of a class, and in turn has its own methods (e.g. try `f.apply` on a function `f`). -Unlike methods, _functions_ are complete objects themselves, making them first-class entities. +_Methods_ are not values that can be passed around, i.e. they can only be called via method application (e.g. `foo(arg1, arg2, ...)`). Methods can be _converted_ to a value by creating a function value that will call the method when supplied with the required arguments. This is known as eta-expansion. -Their syntax is also different. -This example shows how to define a method and a function that perform the same task, determining if the given integer is even: +More concretely: with automatic eta-expansion, the compiler automatically converts any _method reference_, without supplied arguments, to an equivalent _anonymous function_ that will call the method. For example, the reference to `times10` in the code above gets rewritten to `x => times10(x)`, as seen here: -{% tabs fun_3 %} -{% tab 'Scala 2 and 3' for=fun_3 %} +{% tabs fun_2_expanded %} +{% tab 'Scala 2 and 3' %} ```scala -def isEvenMethod(i: Int) = i % 2 == 0 // a method -val isEvenFunction = (i: Int) => i % 2 == 0 // a function +def times10(i: Int) = i * 10 +List(1, 2, 3).map(x => times10(x)) // eta expansion of `.map(times10)` ``` {% endtab %} {% endtabs %} -The function truly is an object, so you can use it just like any other variable, such as putting it in a list: +> For the curious, the term eta-expansion has its origins in the [Lambda Calculus](https://en.wikipedia.org/wiki/Lambda_calculus). + +## When does eta-expansion happen? -{% tabs fun_4 %} -{% tab 'Scala 2 and 3' for=fun_4 %} +Automatic eta-expansion is a desugaring that is context-dependent (i.e. the expansion conditionally activates, depending on the surrounding code of the method reference.) + +{% tabs fun_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} +In Scala 2 eta-expansion only occurs automatically when the expected type is a function type. +For example, the following will fail: ```scala -val functions = List(isEvenFunction) +def isLessThan(x: Int, y: Int): Boolean = x < y + +val methods = List(isLessThan) +// ^^^^^^^^^^ +// error: missing argument list for method isLessThan +// Unapplied methods are only converted to functions when a function type is expected. +// You can make this conversion explicit by writing `isLessThan _` or `isLessThan(_,_)` instead of `isLessThan`. ``` +See [below](#manual-eta-expansion) for how to solve this issue with manual eta-expansion. {% endtab %} -{% endtabs %} -{% tabs fun_5 class=tabs-scala-version %} -{% tab 'Scala 2' for=fun_5 %} +{% tab 'Scala 3' %} + +New to Scala 3, method references can be used everywhere as a value, they will be automatically converted to a function object with a matching type. e.g. ```scala -// this example shows the Scala 2 error message -val methods = List(isEvenMethod) - ^ -error: missing argument list for method isEvenMethod -Unapplied methods are only converted to functions when a function type is expected. -You can make this conversion explicit by writing `isEvenMethod _` or `isEvenMethod(_)` instead of `isEvenMethod`. -``` +def isLessThan(x: Int, y: Int): Boolean = x < y -Conversely, a method technically isn’t an object, so in Scala 2 you couldn’t put a method in a `List`, at least not directly, as shown in this example: +val methods = List(isLessThan) // works +``` {% endtab %} +{% endtabs %} + +## Manual eta-expansion -{% tab 'Scala 3' for=fun_5 %} +You can always manually eta-expand a method to a function value, here are some examples how: + +{% tabs fun_6 class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -val functions = List(isEvenFunction) // works -val methods = List(isEvenMethod) // works +val methodsA = List(isLessThan _) // way 1: expand all parameters +val methodsB = List(isLessThan(_, _)) // way 2: wildcard application +val methodsC = List((x, y) => isLessThan(x, y)) // way 3: anonymous function ``` -The important part for Scala 3 is that the Eta Expansion technology is improved, so now when you attempt to use a method as a variable, it just works---you don’t have to handle the manual conversion yourself. +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +val methodsA = List(isLessThan(_, _)) // way 1: wildcard application +val methodsB = List((x, y) => isLessThan(x, y)) // way 2: anonymous function +``` {% endtab %} {% endtabs %} +## Summary + For the purpose of this introductory book, the important things to know are: -- Eta Expansion is the Scala technology that lets you use methods just like functions -- The technology has been improved in Scala 3 to be almost completely seamless +- eta-expansion is a helpful desugaring that lets you use methods just like functions, +- the automatic eta-expansion been improved in Scala 3 to be almost completely seamless. For more details on how this works, see the [Eta Expansion page][eta_expansion] in the Reference documentation.