Skip to content

Commit ffe9959

Browse files
authored
Merge pull request #699 from travissarles/higher-order-functions
Rewrote higher-order-functions
2 parents ac4d26f + 72d6fc4 commit ffe9959

File tree

1 file changed

+93
-12
lines changed

1 file changed

+93
-12
lines changed

_tour/higher-order-functions.md

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,113 @@ previous-page: mixin-class-composition
1313
redirect_from: "/tutorials/tour/higher-order-functions.html"
1414
---
1515

16-
Scala allows the definition of higher-order functions. These are functions that _take other functions as parameters_, or whose _result is a function_. Here is a function `apply` which takes another function `f` and a value `v` and applies function `f` to `v`:
17-
16+
Higher order functions take other functions as parameters or return a function as
17+
a result. This is possible because functions are first-class values in Scala.
18+
One of the most common examples is the higher-order
19+
function `map` which is available for collections in Scala.
1820
```tut
19-
def apply(f: Int => String, v: Int) = f(v)
21+
val salaries = Seq(20000, 70000, 40000)
22+
val doubleSalary = (x: Int) => x * 2
23+
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)
2024
```
25+
`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. One line 3, the function `doubleSalary` gets applied to each element in the
26+
list of salaries.
2127

22-
_Note: methods are automatically coerced to functions if the context requires this._
28+
To shrink the code, we could make the function anonymous and pass it directly as
29+
an argument to map:
30+
```
31+
val salaries = Seq(20000, 70000, 40000)
32+
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)
33+
```
34+
Notice how `x` is not declared as an Int in the above example. That's because the
35+
compiler can infer the type based on the type of function map expects. An even more idiomatic way to write the same piece of code would be
2336

2437
Here is another example:
2538

2639
```tut
27-
class Decorator(left: String, right: String) {
28-
def layout[A](x: A) = left + x.toString() + right
40+
val salaries = Seq(20000, 70000, 40000)
41+
val newSalaries = salaries.map(_ * 2)
42+
```
43+
Since the Scala compiler already knows the type of the parameters (a single Int),
44+
you just need to provide the right side of the function. The only
45+
caveat is that you need to use `_` in place of a parameter name (it was `x` in
46+
the previous example).
47+
48+
## Coercing methods into functions
49+
It is also possible to pass methods as arguments to higher-order functions because
50+
the Scala compiler will coerce the method into a function.
51+
```
52+
case class WeeklyWeatherForecast(temperatures: Seq[Double]) {
53+
54+
private def convertCtoF(temp: Double) = temp * 1.8 + 32
55+
56+
def forecastInFahrenheit: Double = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
2957
}
58+
```
59+
Here the method `convertCtoF` is passed to forecastInFahrenheit This is possible because the compiler coerces `convertCtoF` to the function `x => convertCtoF(x)` (note: `x` will
60+
be a generated name which is guaranteed to be unique within its scope).
61+
62+
## Functions that accept functions
63+
One reason to use higher-order functions is to reduce redundant code. Let's say you wanted functions that could raise someone's salaries by various factors. Without creating a higher-order function,
64+
it might look something like this:
65+
66+
```tut
67+
object SalaryRaiser {
68+
69+
def smallPromotion(salaries: List[Double]): List[Double] =
70+
salaries.map(salary => salary * 1.1)
71+
72+
def greatPromotion(salaries: List[Double]): List[Double] =
73+
salaries.map(salary => salary * math.log(salary))
3074
31-
object FunTest extends App {
32-
def apply(f: Int => String, v: Int) = f(v)
33-
val decorator = new Decorator("[", "]")
34-
println(apply(decorator.layout, 7))
75+
def hugePromotion(salaries: List[Double]): List[Double] =
76+
salaries.map(salary => salary * salary)
3577
}
3678
```
3779

3880
Execution yields the output:
3981

82+
Notice how each of the three methods vary only by the multiplication factor. To simplify,
83+
you can extract the repeated code into a higher-order function like so:
84+
85+
```tut
86+
object SalaryRaiser {
87+
88+
private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
89+
salaries.map(promotionFunction)
90+
91+
def smallPromotion(salaries: List[Double]): List[Double] =
92+
promotion(salaries, salary => salary * 1.1)
93+
94+
def bigPromotion(salaries: List[Double]): List[Double] =
95+
promotion(salaries, salary => salary * math.log(salary))
96+
97+
def hugePromotion(salaries: List[Double]): List[Double] =
98+
promotion(salaries, salary => salary * salary)
99+
}
40100
```
41-
[7]
101+
102+
The new function, `promotion`, takes the salaries plus a function of type `Double => Double`
103+
(i.e. a function that takes a Double and returns a Double) and returns the product.
104+
105+
## Functions that return functions
106+
107+
There are certain cases where you want to generate a function. Here's an example
108+
of a method that returns a function.
109+
110+
```tut
111+
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
112+
val schema = if (ssl) "https://" else "http://"
113+
(endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
114+
}
115+
116+
val domainName = "www.example.com"
117+
def getURL = urlBuilder(ssl=true, domainName)
118+
val endpoint = "users"
119+
val query = "id=1"
120+
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String
42121
```
43122

44-
In this example, the method `decorator.layout` is coerced automatically to a value of type `Int => String` as required by method `apply`. Please note that method `decorator.layout` is a _polymorphic method_ (i.e. it abstracts over some of its signature types) and the Scala compiler has to instantiate its method type first appropriately.
123+
Notice the return type of urlBuilder `(String, String) => String`. This means that
124+
the returned anonymous function takes two Strings and returns a String. In this case,
125+
the returned anonymous function is `(endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"`.

0 commit comments

Comments
 (0)