Skip to content

Commit 525b8ee

Browse files
authored
Merge pull request #2525 from flomebul/patch-1
Add code tabs for _tour/higher-order-functions
2 parents d46fd22 + 771d3c2 commit 525b8ee

File tree

1 file changed

+112
-6
lines changed

1 file changed

+112
-6
lines changed

_tour/higher-order-functions.md

+112-6
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,50 @@ In a pure Object Oriented world a good practice is to avoid exposing methods par
2020

2121
One of the most common examples is the higher-order
2222
function `map` which is available for collections in Scala.
23-
```scala mdoc
24-
val salaries = Seq(20000, 70000, 40000)
23+
24+
{% tabs map_example_1 class=tabs-scala-version %}
25+
26+
{% tab 'Scala 2 and 3' for=map_example_1 %}
27+
```scala
28+
val salaries = Seq(20_000, 70_000, 40_000)
2529
val doubleSalary = (x: Int) => x * 2
2630
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)
2731
```
32+
{% endtab %}
33+
34+
{% endtabs %}
35+
2836
`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
2937
list of salaries.
3038

3139
To shrink the code, we could make the function anonymous and pass it directly as
3240
an argument to map:
33-
```scala:nest
34-
val salaries = Seq(20000, 70000, 40000)
41+
42+
{% tabs map_example_2 class=tabs-scala-version %}
43+
44+
{% tab 'Scala 2 and 3' for=map_example_2 %}
45+
```scala
46+
val salaries = Seq(20_000, 70_000, 40_000)
3547
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)
3648
```
49+
{% endtab %}
50+
51+
{% endtabs %}
52+
3753
Notice how `x` is not declared as an Int in the above example. That's because the
3854
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:
3955

40-
```scala mdoc:nest
41-
val salaries = Seq(20000, 70000, 40000)
56+
{% tabs map_example_3 class=tabs-scala-version %}
57+
58+
{% tab 'Scala 2 and 3' for=map_example_3 %}
59+
```scala
60+
val salaries = Seq(20_000, 70_000, 40_000)
4261
val newSalaries = salaries.map(_ * 2)
4362
```
63+
{% endtab %}
64+
65+
{% endtabs %}
66+
4467
Since the Scala compiler already knows the type of the parameters (a single Int),
4568
you just need to provide the right side of the function. The only
4669
caveat is that you need to use `_` in place of a parameter name (it was `x` in
@@ -49,6 +72,10 @@ the previous example).
4972
## Coercing methods into functions
5073
It is also possible to pass methods as arguments to higher-order functions because
5174
the Scala compiler will coerce the method into a function.
75+
76+
{% tabs Coercing_methods_into_functions class=tabs-scala-version %}
77+
78+
{% tab 'Scala 2' for=Coercing_methods_into_functions %}
5279
```scala mdoc
5380
case class WeeklyWeatherForecast(temperatures: Seq[Double]) {
5481

@@ -57,13 +84,30 @@ case class WeeklyWeatherForecast(temperatures: Seq[Double]) {
5784
def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
5885
}
5986
```
87+
{% endtab %}
88+
89+
{% tab 'Scala 3' for=Coercing_methods_into_functions %}
90+
```scala
91+
case class WeeklyWeatherForecast(temperatures: Seq[Double]):
92+
93+
private def convertCtoF(temp: Double) = temp * 1.8 + 32
94+
95+
def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
96+
```
97+
{% endtab %}
98+
99+
{% endtabs %}
100+
60101
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
61102
be a generated name which is guaranteed to be unique within its scope).
62103

63104
## Functions that accept functions
64105
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,
65106
it might look something like this:
66107

108+
{% tabs Functions_that_accept_functions_1 class=tabs-scala-version %}
109+
110+
{% tab 'Scala 2' for=Functions_that_accept_functions_1 %}
67111
```scala mdoc
68112
object SalaryRaiser {
69113

@@ -77,10 +121,31 @@ object SalaryRaiser {
77121
salaries.map(salary => salary * salary)
78122
}
79123
```
124+
{% endtab %}
125+
126+
{% tab 'Scala 3' for=Functions_that_accept_functions_1 %}
127+
```scala
128+
object SalaryRaiser:
129+
130+
def smallPromotion(salaries: List[Double]): List[Double] =
131+
salaries.map(salary => salary * 1.1)
132+
133+
def greatPromotion(salaries: List[Double]): List[Double] =
134+
salaries.map(salary => salary * math.log(salary))
135+
136+
def hugePromotion(salaries: List[Double]): List[Double] =
137+
salaries.map(salary => salary * salary)
138+
```
139+
{% endtab %}
140+
141+
{% endtabs %}
80142

81143
Notice how each of the three methods vary only by the multiplication factor. To simplify,
82144
you can extract the repeated code into a higher-order function like so:
83145

146+
{% tabs Functions_that_accept_functions_2 class=tabs-scala-version %}
147+
148+
{% tab 'Scala 2' for=Functions_that_accept_functions_2 %}
84149
```scala mdoc:nest
85150
object SalaryRaiser {
86151

@@ -97,6 +162,27 @@ object SalaryRaiser {
97162
promotion(salaries, salary => salary * salary)
98163
}
99164
```
165+
{% endtab %}
166+
167+
{% tab 'Scala 3' for=Functions_that_accept_functions_2 %}
168+
```scala
169+
object SalaryRaiser:
170+
171+
private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
172+
salaries.map(promotionFunction)
173+
174+
def smallPromotion(salaries: List[Double]): List[Double] =
175+
promotion(salaries, salary => salary * 1.1)
176+
177+
def greatPromotion(salaries: List[Double]): List[Double] =
178+
promotion(salaries, salary => salary * math.log(salary))
179+
180+
def hugePromotion(salaries: List[Double]): List[Double] =
181+
promotion(salaries, salary => salary * salary)
182+
```
183+
{% endtab %}
184+
185+
{% endtabs %}
100186

101187
The new method, `promotion`, takes the salaries plus a function of type `Double => Double`
102188
(i.e. a function that takes a Double and returns a Double) and returns the product.
@@ -108,6 +194,9 @@ Methods and functions usually express behaviours or data transformations, theref
108194
There are certain cases where you want to generate a function. Here's an example
109195
of a method that returns a function.
110196

197+
{% tabs Functions_that_return_functions class=tabs-scala-version %}
198+
199+
{% tab 'Scala 2' for=Functions_that_return_functions %}
111200
```scala mdoc
112201
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
113202
val schema = if (ssl) "https://" else "http://"
@@ -120,6 +209,23 @@ val endpoint = "users"
120209
val query = "id=1"
121210
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String
122211
```
212+
{% endtab %}
213+
214+
{% tab 'Scala 3' for=Functions_that_return_functions %}
215+
```scala
216+
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String =
217+
val schema = if ssl then "https://" else "http://"
218+
(endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
219+
220+
val domainName = "www.example.com"
221+
def getURL = urlBuilder(ssl=true, domainName)
222+
val endpoint = "users"
223+
val query = "id=1"
224+
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String
225+
```
226+
{% endtab %}
227+
228+
{% endtabs %}
123229

124230
Notice the return type of urlBuilder `(String, String) => String`. This means that
125231
the returned anonymous function takes two Strings and returns a String. In this case,

0 commit comments

Comments
 (0)