{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "

Previous

\n", "

Next

\n", "

Tour of Scala

\n", "
\n", "\n", "# Higher-order Functions\n", "\n", "Higher order functions take other functions as parameters or return a function as\n", "a result. This is possible because functions are first-class values in Scala.\n", "The terminology can get a bit confusing at this point, and we use the phrase\n", "\"higher order function\" for both methods and functions that take functions as parameters\n", "or that return a function.\n", "\n", "One of the most common examples is the higher-order\n", "function `map` which is available for collections in Scala." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "\u001b[36msalaries\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m20000\u001b[39m, \u001b[32m70000\u001b[39m, \u001b[32m40000\u001b[39m)\n", "\u001b[36mdoubleSalary\u001b[39m: \u001b[32mInt\u001b[39m => \u001b[32mInt\u001b[39m = ammonite.$sess.cmd0$Helper$$Lambda$2345/597765378@4a42c7d2\n", "\u001b[36mnewSalaries\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m40000\u001b[39m, \u001b[32m140000\u001b[39m, \u001b[32m80000\u001b[39m)" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val salaries = Seq(20000, 70000, 40000)\n", "val doubleSalary = (x: Int) => x * 2\n", "val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`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\n", "list of salaries.\n", "\n", "To shrink the code, we could make the function anonymous and pass it directly as\n", "an argument to map:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "\u001b[36msalaries\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m20000\u001b[39m, \u001b[32m70000\u001b[39m, \u001b[32m40000\u001b[39m)\n", "\u001b[36mnewSalaries\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m40000\u001b[39m, \u001b[32m140000\u001b[39m, \u001b[32m80000\u001b[39m)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val salaries = Seq(20000, 70000, 40000)\n", "val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice how `x` is not declared as an Int in the above example. That's because the\n", "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:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "\u001b[36msalaries\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m20000\u001b[39m, \u001b[32m70000\u001b[39m, \u001b[32m40000\u001b[39m)\n", "\u001b[36mnewSalaries\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m40000\u001b[39m, \u001b[32m140000\u001b[39m, \u001b[32m80000\u001b[39m)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val salaries = Seq(20000, 70000, 40000)\n", "val newSalaries = salaries.map(_ * 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the Scala compiler already knows the type of the parameters (a single Int),\n", " you just need to provide the right side of the function. The only\n", "caveat is that you need to use `_` in place of a parameter name (it was `x` in\n", "the previous example).\n", "\n", "## Coercing methods into functions\n", "It is also possible to pass methods as arguments to higher-order functions because\n", "the Scala compiler will coerce the method into a function." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass\u001b[39m \u001b[36mWeeklyWeatherForecast\u001b[39m" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "case class WeeklyWeatherForecast(temperatures: Seq[Double]) {\n", "\n", " private def convertCtoF(temp: Double) = temp * 1.8 + 32\n", "\n", " def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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\n", " be a generated name which is guaranteed to be unique within its scope).\n", "\n", "## Functions that accept functions\n", "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,\n", "it might look something like this:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mobject\u001b[39m \u001b[36mSalaryRaiser\u001b[39m" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "object SalaryRaiser {\n", "\n", " def smallPromotion(salaries: List[Double]): List[Double] =\n", " salaries.map(salary => salary * 1.1)\n", "\n", " def greatPromotion(salaries: List[Double]): List[Double] =\n", " salaries.map(salary => salary * math.log(salary))\n", "\n", " def hugePromotion(salaries: List[Double]): List[Double] =\n", " salaries.map(salary => salary * salary)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice how each of the three methods vary only by the multiplication factor. To simplify,\n", "you can extract the repeated code into a higher-order function like so:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mobject\u001b[39m \u001b[36mSalaryRaiser\u001b[39m" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "object SalaryRaiser {\n", "\n", " private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =\n", " salaries.map(promotionFunction)\n", "\n", " def smallPromotion(salaries: List[Double]): List[Double] =\n", " promotion(salaries, salary => salary * 1.1)\n", "\n", " def bigPromotion(salaries: List[Double]): List[Double] =\n", " promotion(salaries, salary => salary * math.log(salary))\n", "\n", " def hugePromotion(salaries: List[Double]): List[Double] =\n", " promotion(salaries, salary => salary * salary)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The new method, `promotion`, takes the salaries plus a function of type `Double => Double`\n", "(i.e. a function that takes a Double and returns a Double) and returns the product.\n", "\n", "## Functions that return functions\n", "\n", "There are certain cases where you want to generate a function. Here's an example\n", "of a method that returns a function." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36murlBuilder\u001b[39m\n", "\u001b[36mdomainName\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"www.example.com\"\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mgetURL\u001b[39m\n", "\u001b[36mendpoint\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"users\"\u001b[39m\n", "\u001b[36mquery\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"id=1\"\u001b[39m\n", "\u001b[36murl\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"https://www.example.com/users?id=1\"\u001b[39m" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {\n", " val schema = if (ssl) \"https://\" else \"http://\"\n", " (endpoint: String, query: String) => s\"$schema$domainName/$endpoint?$query\"\n", "}\n", "\n", "val domainName = \"www.example.com\"\n", "def getURL = urlBuilder(ssl=true, domainName)\n", "val endpoint = \"users\"\n", "val query = \"id=1\"\n", "val url = getURL(endpoint, query) // \"https://www.example.com/users?id=1\": String" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice the return type of urlBuilder `(String, String) => String`. This means that\n", "the returned anonymous function takes two Strings and returns a String. In this case,\n", "the returned anonymous function is `(endpoint: String, query: String) => s\"https://www.example.com/$endpoint?$query\"`.\n", "

Previous

\n", "

Next

\n", "

Tour of Scala

\n", "
" ] } ], "metadata": { "kernelspec": { "display_name": "Scala (2.13)", "language": "scala", "name": "scala213" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.13.1" } }, "nbformat": 4, "nbformat_minor": 4 }