-
Notifications
You must be signed in to change notification settings - Fork 1k
Add code tabs for _tour/higher-order-functions #2525
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
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
8e87cbb
Add code tabs for _tour/higher-order-functions
flomebul 382a5cc
Update higher-order-functions.md
flomebul 68f5873
Update higher-order-functions.md
flomebul 6d0098b
Update higher-order-functions.md
flomebul 6968d4b
Update higher-order-functions.md
flomebul 085fd80
Update higher-order-functions.md
flomebul 771d3c2
Update higher-order-functions.md
flomebul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,27 +20,72 @@ In a pure Object Oriented world a good practice is to avoid exposing methods par | |
|
||
One of the most common examples is the higher-order | ||
function `map` which is available for collections in Scala. | ||
|
||
{% tabs map_example_1 class=tabs-scala-version %} | ||
|
||
{% tab 'Scala 2' for=map_example_1 %} | ||
```scala mdoc | ||
val salaries = Seq(20000, 70000, 40000) | ||
val doubleSalary = (x: Int) => x * 2 | ||
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=map_example_1 %} | ||
```scala | ||
val salaries = Seq(20_000, 70_000, 40_000) | ||
val doubleSalary = (x: Int) => x * 2 | ||
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
|
||
`doubleSalary` is a function which takes a single Int, `x`, and returns `x * 2`. In general, the tuple on the left of the arrow `=>` is a parameter list and the value of the expression on the right is what gets returned. On line 3, the function `doubleSalary` gets applied to each element in the | ||
list of salaries. | ||
|
||
To shrink the code, we could make the function anonymous and pass it directly as | ||
an argument to map: | ||
|
||
{% tabs map_example_2 class=tabs-scala-version %} | ||
|
||
{% tab 'Scala 2' for=map_example_2 %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, one tab please |
||
```scala:nest | ||
val salaries = Seq(20000, 70000, 40000) | ||
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=map_example_2 %} | ||
```scala | ||
val salaries = Seq(20_000, 70_000, 40_000) | ||
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
|
||
Notice how `x` is not declared as an Int in the above example. That's because the | ||
compiler can infer the type based on the type of function map expects (see [Currying](/tour/multiple-parameter-lists.html)). An even more idiomatic way to write the same piece of code would be: | ||
|
||
{% tabs map_example_3 class=tabs-scala-version %} | ||
|
||
{% tab 'Scala 2' for=map_example_3 %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and here, you can have underscores in scala 2 |
||
```scala mdoc:nest | ||
val salaries = Seq(20000, 70000, 40000) | ||
val newSalaries = salaries.map(_ * 2) | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=map_example_3 %} | ||
```scala | ||
val salaries = Seq(20_000, 70_000, 40_000) | ||
val newSalaries = salaries.map(_ * 2) | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
|
||
Since the Scala compiler already knows the type of the parameters (a single Int), | ||
you just need to provide the right side of the function. The only | ||
caveat is that you need to use `_` in place of a parameter name (it was `x` in | ||
|
@@ -49,6 +94,10 @@ the previous example). | |
## Coercing methods into functions | ||
It is also possible to pass methods as arguments to higher-order functions because | ||
the Scala compiler will coerce the method into a function. | ||
|
||
{% tabs Coercing_methods_into_functions class=tabs-scala-version %} | ||
|
||
{% tab 'Scala 2' for=Coercing_methods_into_functions %} | ||
```scala mdoc | ||
case class WeeklyWeatherForecast(temperatures: Seq[Double]) { | ||
|
||
|
@@ -57,13 +106,30 @@ case class WeeklyWeatherForecast(temperatures: Seq[Double]) { | |
def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF | ||
} | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=Coercing_methods_into_functions %} | ||
```scala | ||
case class WeeklyWeatherForecast(temperatures: Seq[Double]): | ||
|
||
private def convertCtoF(temp: Double) = temp * 1.8 + 32 | ||
|
||
def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
|
||
Here the method `convertCtoF` is passed to the higher order function `map`. This is possible because the compiler coerces `convertCtoF` to the function `x => convertCtoF(x)` (note: `x` will | ||
be a generated name which is guaranteed to be unique within its scope). | ||
|
||
## Functions that accept functions | ||
One reason to use higher-order functions is to reduce redundant code. Let's say you wanted some methods that could raise someone's salaries by various factors. Without creating a higher-order function, | ||
it might look something like this: | ||
|
||
{% tabs Functions_that_accept_functions_1 class=tabs-scala-version %} | ||
|
||
{% tab 'Scala 2' for=Functions_that_accept_functions_1 %} | ||
```scala mdoc | ||
object SalaryRaiser { | ||
|
||
|
@@ -77,10 +143,31 @@ object SalaryRaiser { | |
salaries.map(salary => salary * salary) | ||
} | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=Functions_that_accept_functions_1 %} | ||
```scala | ||
object SalaryRaiser: | ||
|
||
def smallPromotion(salaries: List[Double]): List[Double] = | ||
salaries.map(salary => salary * 1.1) | ||
|
||
def greatPromotion(salaries: List[Double]): List[Double] = | ||
salaries.map(salary => salary * math.log(salary)) | ||
|
||
def hugePromotion(salaries: List[Double]): List[Double] = | ||
salaries.map(salary => salary * salary) | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
|
||
Notice how each of the three methods vary only by the multiplication factor. To simplify, | ||
you can extract the repeated code into a higher-order function like so: | ||
|
||
{% tabs Functions_that_accept_functions_2 class=tabs-scala-version %} | ||
|
||
{% tab 'Scala 2' for=Functions_that_accept_functions_2 %} | ||
```scala mdoc:nest | ||
object SalaryRaiser { | ||
|
||
|
@@ -97,6 +184,27 @@ object SalaryRaiser { | |
promotion(salaries, salary => salary * salary) | ||
} | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=Functions_that_accept_functions_2 %} | ||
```scala | ||
object SalaryRaiser: | ||
|
||
private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] = | ||
salaries.map(promotionFunction) | ||
|
||
def smallPromotion(salaries: List[Double]): List[Double] = | ||
promotion(salaries, salary => salary * 1.1) | ||
|
||
def greatPromotion(salaries: List[Double]): List[Double] = | ||
promotion(salaries, salary => salary * math.log(salary)) | ||
|
||
def hugePromotion(salaries: List[Double]): List[Double] = | ||
promotion(salaries, salary => salary * salary) | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
|
||
The new method, `promotion`, takes the salaries plus a function of type `Double => Double` | ||
(i.e. a function that takes a Double and returns a Double) and returns the product. | ||
|
@@ -108,6 +216,9 @@ Methods and functions usually express behaviours or data transformations, theref | |
There are certain cases where you want to generate a function. Here's an example | ||
of a method that returns a function. | ||
|
||
{% tabs Functions_that_return_functions class=tabs-scala-version %} | ||
|
||
{% tab 'Scala 2' for=Functions_that_return_functions %} | ||
```scala mdoc | ||
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = { | ||
val schema = if (ssl) "https://" else "http://" | ||
|
@@ -120,6 +231,23 @@ val endpoint = "users" | |
val query = "id=1" | ||
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=Functions_that_return_functions %} | ||
```scala | ||
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = | ||
val schema = if ssl then "https://" else "http://" | ||
(endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query" | ||
|
||
val domainName = "www.example.com" | ||
def getURL = urlBuilder(ssl=true, domainName) | ||
val endpoint = "users" | ||
val query = "id=1" | ||
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
|
||
Notice the return type of urlBuilder `(String, String) => String`. This means that | ||
the returned anonymous function takes two Strings and returns a String. In this case, | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
surely this tab could be Scala 2 and 3? (scala 2 supports underscores in numeric literals)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have try, but it was refused by
mdoc
step (see 8e87cbb) if I understand well...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right I didn't check the other commits, that's unfortunate, perhaps the mdoc configuration is outdated. However I'd still prefer to have 1 tab without the underscores - I don't think they are a fundamental improvement because at least for now our syntax highlighting doesn't support it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. If fact, mdoc look like to check with scala 2.12.4, that does not support such constant...
We just have to not use "mdoc". I will perform the change.