-
Notifications
You must be signed in to change notification settings - Fork 1k
Higher #681
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
Higher #681
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,34 +8,115 @@ tutorial: scala-tour | |
num: 8 | ||
next-page: nested-functions | ||
previous-page: anonymous-function-syntax | ||
assumed-knowledge: sequence-comprehensions | ||
--- | ||
|
||
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`: | ||
Higher order functions are functions that take other functions are parameters | ||
or whose result is a function. They can be useful for reducing duplicate code | ||
when you want to create multiple functions with only slightly different functionality. | ||
|
||
One of the most common examples is the higher-order | ||
function `map` available for collections in Scala. | ||
```tut | ||
def apply(f: Int => String, v: Int) = f(v) | ||
val salaries = Seq(20000, 70000, 40000) | ||
val doubleSalary = (x: Int) => x * 2 | ||
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) | ||
``` | ||
In this case, the function `doubleSalary` gets applied to each element in the | ||
list of salaries. A more idiomatic way to write the same piece of code would be | ||
|
||
_Note: methods are automatically coerced to functions if the context requires this._ | ||
|
||
Here is another example: | ||
|
||
```tut | ||
class Decorator(left: String, right: String) { | ||
def layout[A](x: A) = left + x.toString() + right | ||
} | ||
val salaries = Seq(20000, 70000, 40000) | ||
val newSalaries = salaries.map(_ * 2) | ||
``` | ||
The Scala compiler already knows the type of the parameters (a single Int) so | ||
you just need to provide the right side of the function doubleSalary. The only | ||
caveat is that you need to use `_` in place of a parameter name (it was `x` in | ||
the previous example). | ||
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. Well, you don’t have to use Actually, I would first use |
||
|
||
object FunTest extends App { | ||
def apply(f: Int => String, v: Int) = f(v) | ||
val decorator = new Decorator("[", "]") | ||
println(apply(decorator.layout, 7)) | ||
## 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. | ||
``` | ||
case class WeeklyWeatherForecast(temperatures: Seq[Double]) { | ||
|
||
private def convertCtoF(temp: Double) = temp * 1.8 + 32 | ||
|
||
def getForecastInFahrenheit() = temperatures.map(convertCtoF) // <-- passing the method convertCtoF | ||
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. I think you should always add the return type of your method definitions. 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. Also, a convention in Scala is to put parens to methods that take no parameter only if this method actually performs side-effects. Since |
||
} | ||
``` | ||
|
||
Execution yields the output: | ||
Here the method `convertCtoF` is passed to getForecastInFahrenheit This is possible because the compiler coerces `convertCtoF` to `_ => convertCtoF(_)`, i.e. a function. | ||
|
||
## Functions that accept functions | ||
Let's say you wanted a function that could either search for files by directory, | ||
by regular expression, or by a substring. | ||
by file name, extension, or by contents. Without creating a higher-order function, | ||
it might look something like this: | ||
|
||
```tut | ||
object FileFinder { | ||
private def filesHere = (new java.io.File(".")).listFiles | ||
|
||
def filesEndingWith(query: String) = | ||
for (file <- filesHere; if file.getName.endsWith(query)) | ||
yield | ||
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.
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. Also, this example makes use of the What about staying in the context of salaries and define the following methods? 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)) Which can then be generalized as follows: def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
salaries.map(promotionFunction) |
||
|
||
def filesContaining(query: String) = | ||
for (file <- filesHere; if file.getName.contains(query)) | ||
yield file | ||
|
||
def filesMatchingRegex(query: String) = | ||
for (file <- filesHere; if file.getName.matches(query)) | ||
yield file | ||
} | ||
``` | ||
[7] | ||
|
||
Notice how each of the three methods vary only by a single method call. To simplify, | ||
you can extract the repeated code into a higher-order function like so: | ||
|
||
```tut | ||
object FileMatcher { | ||
private def filesHere = (new java.io.File(".")).listFiles | ||
|
||
private def filesMatching(matcher: String => Boolean) = | ||
for (file <- filesHere; if matcher(file.getName)) | ||
yield file | ||
|
||
def filesEndingWith(query: String) = | ||
filesMatching(_.endsWith(query)) | ||
|
||
def filesContaining(query: String) = | ||
filesMatching(_.contains(query)) | ||
|
||
def filesRegex(query: String) = | ||
filesMatching(_.matches(query)) | ||
} | ||
``` | ||
|
||
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. | ||
The new function, `filesMatching`, takes a function of type `String => Boolean` | ||
(i.e. a function that takes a String and returns a Boolean) and returns the Sequence comprehension | ||
created by the `for`/`yield`. | ||
|
||
Credit: Odersky, Martin, Lex Spoon, and Bill Venners. Programming in Scala. Walnut Creek, CA: Artima, 2010. Web. | ||
|
||
## Functions that return functions | ||
There are certain cases where you | ||
```tut | ||
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = { | ||
val schema = if (ssl) "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" | ||
getURL(endpoint, query) // "https://www.example.com/users?id=1": String | ||
``` | ||
|
||
|
||
Caveat | ||
```tut | ||
List(1,3,4).map(MyNum(_)) | ||
``` |
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.
Maybe you should explain a little bit more what the
x => …
syntax means?