From 8e87cbb6dcb3d74077b0703948a1e85ddbe4cd75 Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Thu, 15 Sep 2022 18:44:03 +0200 Subject: [PATCH 1/7] Add code tabs for _tour/higher-order-functions --- _tour/higher-order-functions.md | 111 +++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index f60f910b8f..dfeebf497f 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -20,27 +20,49 @@ 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 %} + +{% tab 'Scala 2 and 3' for=map_example_1 %} ```scala mdoc -val salaries = Seq(20000, 70000, 40000) +val salaries = Seq(20_000, 70_000, 40_000) val doubleSalary = (x: Int) => x * 2 val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) ``` +{% end tab %} + +{% end tabs %} + `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 %} + +{% tab 'Scala 2 and 3' for=map_example_2 %} ```scala:nest -val salaries = Seq(20000, 70000, 40000) +val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) ``` +{% end tab %} + +{% end tabs %} 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 %} + +{% tab 'Scala 2 and 3' for=map_example_3 %} ```scala mdoc:nest -val salaries = Seq(20000, 70000, 40000) +val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(_ * 2) ``` +{% end tab %} + +{% end tabs %} + 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 +71,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 %} + +{% tab 'Scala 2' for=Coercing_methods_into_functions %} ```scala mdoc case class WeeklyWeatherForecast(temperatures: Seq[Double]) { @@ -57,6 +83,20 @@ case class WeeklyWeatherForecast(temperatures: Seq[Double]) { def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF } ``` +{% end tab %} + +{% 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 +``` +{% end tab %} + +{% end tabs %} + 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). @@ -64,6 +104,9 @@ Here the method `convertCtoF` is passed to the higher order function `map`. This 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 %} + +{% tab 'Scala 2' for=Functions_that_accept_functions_1 %} ```scala mdoc object SalaryRaiser { @@ -77,10 +120,31 @@ object SalaryRaiser { salaries.map(salary => salary * salary) } ``` +{% end tab %} + +{% 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) +``` +{% end tab %} + +{% end tabs %} 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 %} + +{% tab 'Scala 2' for=Functions_that_accept_functions_2 %} ```scala mdoc:nest object SalaryRaiser { @@ -97,6 +161,27 @@ object SalaryRaiser { promotion(salaries, salary => salary * salary) } ``` +{% end tab %} + +{% 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) +``` +{% end tab %} + +{% end tabs %} 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 +193,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 %} + +{% 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 +208,23 @@ val endpoint = "users" val query = "id=1" val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String ``` +{% end tab %} + +{% 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 +``` +{% end tab %} + +{% end tabs %} 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, From 382a5cc0fed3405b5390d63cdd1a2187288425f9 Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Thu, 15 Sep 2022 18:54:59 +0200 Subject: [PATCH 2/7] Update higher-order-functions.md --- _tour/higher-order-functions.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index dfeebf497f..f3f0c3ccb4 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -23,8 +23,16 @@ function `map` which is available for collections in Scala. {% tabs map_example_1 %} -{% tab 'Scala 2 and 3' for=map_example_1 %} +{% 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) +``` +{% end tab %} + +{% 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) @@ -41,21 +49,36 @@ an argument to map: {% tabs map_example_2 %} -{% tab 'Scala 2 and 3' for=map_example_2 %} +{% tab 'Scala 2' for=map_example_2 %} ```scala:nest +val salaries = Seq(20000, 70000, 40000) +val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) +``` +{% end tab %} + +{% 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) ``` {% end tab %} {% end tabs %} + 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 %} -{% tab 'Scala 2 and 3' for=map_example_3 %} +{% tab 'Scala 2' for=map_example_3 %} ```scala mdoc:nest +val salaries = Seq(20000, 70000, 40000) +val newSalaries = salaries.map(_ * 2) +``` +{% end tab %} + +{% tab 'Scala 3' for=map_example_3 %} +```scala mdoc val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(_ * 2) ``` From 68f5873042969ebaa24cb3e1fb16bfa29919f163 Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Thu, 15 Sep 2022 19:06:26 +0200 Subject: [PATCH 3/7] Update higher-order-functions.md --- _tour/higher-order-functions.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index f3f0c3ccb4..6b170de49b 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -21,7 +21,7 @@ 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 %} +{% tabs map_example_1 class=tabs-scala-version %} {% tab 'Scala 2' for=map_example_1 %} ```scala mdoc @@ -47,7 +47,7 @@ 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 %} +{% tabs map_example_2 class=tabs-scala-version %} {% tab 'Scala 2' for=map_example_2 %} ```scala:nest @@ -68,7 +68,7 @@ val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) 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 %} +{% tabs map_example_3 class=tabs-scala-version %} {% tab 'Scala 2' for=map_example_3 %} ```scala mdoc:nest @@ -95,7 +95,7 @@ the previous example). 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 %} +{% tabs Coercing_methods_into_functions class=tabs-scala-version %} {% tab 'Scala 2' for=Coercing_methods_into_functions %} ```scala mdoc @@ -127,7 +127,7 @@ Here the method `convertCtoF` is passed to the higher order function `map`. This 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 %} +{% tabs Functions_that_accept_functions_1 class=tabs-scala-version %} {% tab 'Scala 2' for=Functions_that_accept_functions_1 %} ```scala mdoc @@ -165,7 +165,7 @@ object SalaryRaiser: 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 %} +{% tabs Functions_that_accept_functions_2 class=tabs-scala-version %} {% tab 'Scala 2' for=Functions_that_accept_functions_2 %} ```scala mdoc:nest @@ -216,7 +216,7 @@ 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 %} +{% tabs Functions_that_return_functions class=tabs-scala-version %} {% tab 'Scala 2' for=Functions_that_return_functions %} ```scala mdoc From 6d0098be8b0b88921cf20091cef680629574d70f Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Thu, 15 Sep 2022 19:10:12 +0200 Subject: [PATCH 4/7] Update higher-order-functions.md --- _tour/higher-order-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index 6b170de49b..563d30ebaf 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -78,7 +78,7 @@ val newSalaries = salaries.map(_ * 2) {% end tab %} {% tab 'Scala 3' for=map_example_3 %} -```scala mdoc +```scala val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(_ * 2) ``` From 6968d4bc33217a0ce851fa610641923205e11811 Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Thu, 15 Sep 2022 19:14:57 +0200 Subject: [PATCH 5/7] Update higher-order-functions.md --- _tour/higher-order-functions.md | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index 563d30ebaf..099f0dea95 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -29,7 +29,7 @@ val salaries = Seq(20000, 70000, 40000) val doubleSalary = (x: Int) => x * 2 val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) ``` -{% end tab %} +{% endtab %} {% tab 'Scala 3' for=map_example_1 %} ```scala @@ -37,9 +37,9 @@ val salaries = Seq(20_000, 70_000, 40_000) val doubleSalary = (x: Int) => x * 2 val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) ``` -{% end tab %} +{% endtab %} -{% end tabs %} +{% 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. @@ -54,16 +54,16 @@ an argument to map: val salaries = Seq(20000, 70000, 40000) val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) ``` -{% end tab %} +{% 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) ``` -{% end tab %} +{% endtab %} -{% end tabs %} +{% 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: @@ -75,16 +75,16 @@ compiler can infer the type based on the type of function map expects (see [Curr val salaries = Seq(20000, 70000, 40000) val newSalaries = salaries.map(_ * 2) ``` -{% end tab %} +{% endtab %} {% tab 'Scala 3' for=map_example_3 %} ```scala val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(_ * 2) ``` -{% end tab %} +{% endtab %} -{% end tabs %} +{% 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 @@ -106,7 +106,7 @@ case class WeeklyWeatherForecast(temperatures: Seq[Double]) { def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF } ``` -{% end tab %} +{% endtab %} {% tab 'Scala 3' for=Coercing_methods_into_functions %} ```scala @@ -116,9 +116,9 @@ case class WeeklyWeatherForecast(temperatures: Seq[Double]): def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF ``` -{% end tab %} +{% endtab %} -{% end tabs %} +{% 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). @@ -143,7 +143,7 @@ object SalaryRaiser { salaries.map(salary => salary * salary) } ``` -{% end tab %} +{% endtab %} {% tab 'Scala 3' for=Functions_that_accept_functions_1 %} ```scala @@ -158,9 +158,9 @@ object SalaryRaiser: def hugePromotion(salaries: List[Double]): List[Double] = salaries.map(salary => salary * salary) ``` -{% end tab %} +{% endtab %} -{% end tabs %} +{% 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: @@ -184,7 +184,7 @@ object SalaryRaiser { promotion(salaries, salary => salary * salary) } ``` -{% end tab %} +{% endtab %} {% tab 'Scala 3' for=Functions_that_accept_functions_2 %} ```scala @@ -202,9 +202,9 @@ object SalaryRaiser: def hugePromotion(salaries: List[Double]): List[Double] = promotion(salaries, salary => salary * salary) ``` -{% end tab %} +{% endtab %} -{% end tabs %} +{% 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. @@ -231,7 +231,7 @@ val endpoint = "users" val query = "id=1" val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String ``` -{% end tab %} +{% endtab %} {% tab 'Scala 3' for=Functions_that_return_functions %} ```scala @@ -245,9 +245,9 @@ val endpoint = "users" val query = "id=1" val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String ``` -{% end tab %} +{% endtab %} -{% end tabs %} +{% 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, From 085fd8083991775a6a041168bee38e85cfebb0d8 Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Fri, 16 Sep 2022 15:14:03 +0200 Subject: [PATCH 6/7] Update higher-order-functions.md --- _tour/higher-order-functions.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index 099f0dea95..d34144eb0f 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -23,16 +23,8 @@ function `map` which is available for collections in Scala. {% tabs map_example_1 class=tabs-scala-version %} -{% tab 'Scala 2' for=map_example_1 %} +{% tab 'Scala 2 and 3' 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) @@ -49,15 +41,8 @@ an argument to map: {% tabs map_example_2 class=tabs-scala-version %} -{% tab 'Scala 2' for=map_example_2 %} -```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 +{% tab 'Scala 2 and 3' for=map_example_2 %} +```scala mdoc:nest val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) ``` @@ -70,15 +55,8 @@ compiler can infer the type based on the type of function map expects (see [Curr {% tabs map_example_3 class=tabs-scala-version %} -{% tab 'Scala 2' for=map_example_3 %} +{% tab 'Scala 2 and 3' for=map_example_3 %} ```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) ``` From 771d3c281c12a0103409f7b95e175207d7f6a86b Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Fri, 16 Sep 2022 15:16:35 +0200 Subject: [PATCH 7/7] Update higher-order-functions.md --- _tour/higher-order-functions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index d34144eb0f..3756d6257c 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -24,7 +24,7 @@ function `map` which is available for collections in Scala. {% tabs map_example_1 class=tabs-scala-version %} {% tab 'Scala 2 and 3' for=map_example_1 %} -```scala mdoc +```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) @@ -42,7 +42,7 @@ an argument to map: {% tabs map_example_2 class=tabs-scala-version %} {% tab 'Scala 2 and 3' for=map_example_2 %} -```scala mdoc:nest +```scala val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) ``` @@ -56,7 +56,7 @@ compiler can infer the type based on the type of function map expects (see [Curr {% tabs map_example_3 class=tabs-scala-version %} {% tab 'Scala 2 and 3' for=map_example_3 %} -```scala mdoc:nest +```scala val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(_ * 2) ```