diff --git a/_overviews/scala3-book/fun-hofs.md b/_overviews/scala3-book/fun-hofs.md index f976513ecf..484193a4c2 100644 --- a/_overviews/scala3-book/fun-hofs.md +++ b/_overviews/scala3-book/fun-hofs.md @@ -40,9 +40,13 @@ To understand how higher-order functions work, it helps to dig into an example. For instance, you can understand the type of functions `filter` accepts by looking at its Scaladoc. Here’s the `filter` definition in the `List[A]` class: +{% tabs filter-definition %} +{% tab 'Scala 2 and 3' %} ```scala -def filter(p: (A) => Boolean): List[A] +def filter(p: A => Boolean): List[A] ``` +{% endtab %} +{% endtabs %} This states that `filter` is a method that takes a function parameter named `p`. By convention, `p` stands for a *predicate*, which is just a function that returns a `Boolean` value. @@ -53,14 +57,14 @@ At this point, if you don’t know the purpose of the `filter` method, all you Looking specifically at the function parameter `p`, this part of `filter`’s description: ```scala -p: (A) => Boolean +p: A => Boolean ``` means that whatever function you pass in must take the type `A` as an input parameter and return a `Boolean`. So if your list is a `List[Int]`, you can replace the generic type `A` with `Int`, and read that signature like this: ```scala -p: (Int) => Boolean +p: Int => Boolean ``` Because `isEven` has this type---it transforms an input `Int` into a resulting `Boolean`---it can be used with `filter`. @@ -87,9 +91,13 @@ To create a method that takes a function parameter, all you have to do is: To demonstrate this, here’s a method that that takes an input parameter named `f`, where `f` is a function: +{% tabs sayHello-definition %} +{% tab 'Scala 2 and 3' %} ```scala def sayHello(f: () => Unit): Unit = f() ``` +{% endtab %} +{% endtabs %} This portion of the code---the *type signature*---states that `f` is a function, and defines the types of functions the `sayHello` method will accept: @@ -109,15 +117,24 @@ Here’s how this works: Now that we’ve defined `sayHello`, let’s create a function to match `f`’s signature so we can test it. The following function takes no input parameters and returns nothing, so it matches `f`’s type signature: +{% tabs helloJoe-definition %} +{% tab 'Scala 2 and 3' %} ```scala def helloJoe(): Unit = println("Hello, Joe") ``` +{% endtab %} +{% endtabs %} + Because the type signatures match, you can pass `helloJoe` into `sayHello`: +{% tabs sayHello-usage %} +{% tab 'Scala 2 and 3' %} ```scala sayHello(helloJoe) // prints "Hello, Joe" ``` +{% endtab %} +{% endtabs %} If you’ve never done this before, congratulations: You just defined a method named `sayHello` that takes a function as an input parameter, and then invokes that function in its method body. @@ -128,16 +145,24 @@ You just defined a method named `sayHello` that takes a function as an input par It’s important to know that the beauty of this approach is not that `sayHello` can take *one* function as an input parameter; the beauty is that it can take *any* function that matches `f`’s signature. For instance, because this next function takes no input parameters and returns nothing, it also works with `sayHello`: +{% tabs bonjourJulien-definition %} +{% tab 'Scala 2 and 3' %} ```scala def bonjourJulien(): Unit = println("Bonjour, Julien") ``` +{% endtab %} +{% endtabs %} Here it is in the REPL: +{% tabs bonjourJulien-usage %} +{% tab 'Scala 2 and 3' %} ```` scala> sayHello(bonjourJulien) Bonjour, Julien ```` +{% endtab %} +{% endtabs %} This is a good start. The only thing to do now is see a few more examples of how to define different type signatures for function parameters. @@ -148,9 +173,13 @@ The only thing to do now is see a few more examples of how to define different t In this method: +{% tabs sayHello-definition-2 %} +{% tab 'Scala 2 and 3' %} ```scala def sayHello(f: () => Unit): Unit ``` +{% endtab %} +{% endtabs %} We noted that the type signature for `f` is: @@ -163,7 +192,7 @@ We know that this means, “a function that takes no input parameters and return To demonstrate more type signature examples, here’s a function that takes a `String` parameter and returns an `Int`: ```scala -f: (String) => Int +f: String => Int ``` What kinds of functions take a string and return an integer? @@ -179,11 +208,15 @@ Can you imagine what sort of functions match that signature? The answer is that any function that takes two `Int` input parameters and returns an `Int` matches that signature, so all of these “functions” (methods, really) are a match: +{% tabs add-sub-mul-definitions %} +{% tab 'Scala 2 and 3' %} ```scala def add(a: Int, b: Int): Int = a + b def subtract(a: Int, b: Int): Int = a - b def multiply(a: Int, b: Int): Int = a * b ``` +{% endtab %} +{% endtabs %} As you can infer from these examples, the general syntax for defining function parameter type signatures is: @@ -204,29 +237,48 @@ But for a standalone HOF that doesn’t have its own data, it should also accept For instance, here’s a method named `executeNTimes` that has two input parameters: a function, and an `Int`: +{% tabs executeNTimes-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for (i <- 1 to n) f() +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala def executeNTimes(f: () => Unit, n: Int): Unit = for i <- 1 to n do f() ``` +{% endtab %} +{% endtabs %} As the code shows, `executeNTimes` executes the `f` function `n` times. Because a simple `for` loop like this has no return value, `executeNTimes` returns `Unit`. To test `executeNTimes`, define a method that matches `f`’s signature: +{% tabs helloWorld-definition %} +{% tab 'Scala 2 and 3' %} ```scala // a method of type `() => Unit` def helloWorld(): Unit = println("Hello, world") ``` +{% endtab %} +{% endtabs %} Then pass that method into `executeNTimes` along with an `Int`: -```` +{% tabs helloWorld-usage %} +{% tab 'Scala 2 and 3' %} +``` scala> executeNTimes(helloWorld, 3) Hello, world Hello, world Hello, world -```` +``` +{% endtab %} +{% endtabs %} + Excellent. The `executeNTimes` method executes the `helloWorld` function three times. @@ -237,13 +289,20 @@ The `executeNTimes` method executes the `helloWorld` function three times. Your methods can continue to get as complicated as necessary. For example, this method takes a function of type `(Int, Int) => Int`, along with two input parameters: +{% tabs executeAndPrint-definition %} +{% tab 'Scala 2 and 3' %} ```scala def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit = println(f(i, j)) ``` +{% endtab %} +{% endtabs %} + Because these `sum` and `multiply` methods match that type signature, they can be passed into `executeAndPrint` along with two `Int` values: +{% tabs executeAndPrint-usage %} +{% tab 'Scala 2 and 3' %} ```scala def sum(x: Int, y: Int) = x + y def multiply(x: Int, y: Int) = x * y @@ -251,7 +310,8 @@ def multiply(x: Int, y: Int) = x * y executeAndPrint(sum, 3, 11) // prints 14 executeAndPrint(multiply, 3, 9) // prints 27 ``` - +{% endtab %} +{% endtabs %} ## Function type signature consistency @@ -260,9 +320,13 @@ A great thing about learning about Scala’s function type signatures is that th For instance, if you were to write a function that calculates the sum of two integers, you’d write it like this: +{% tabs f-val-definition %} +{% tab 'Scala 2 and 3' %} ```scala val f: (Int, Int) => Int = (a, b) => a + b ``` +{% endtab %} +{% endtabs %} That code consists of the type signature: