Skip to content

Add code tabs for _tour/multiple-parameter-lists.md #2530

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

Merged
merged 1 commit into from
Sep 16, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions _tour/multiple-parameter-lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,44 @@ Methods may have multiple parameter lists.

Here is an example, as defined on the `Iterable` trait in Scala's collections API:

{% tabs foldLeft_definition class=tabs-scala-version %}

{% tab 'Scala 2' for=foldLeft_definition %}
```scala
trait Iterable[A] {
...
def foldLeft[B](z: B)(op: (B, A) => B): B
...
}
```
{% endtab %}

{% tab 'Scala 3' for=foldLeft_definition %}
```scala
trait Iterable[A]:
...
def foldLeft[B](z: B)(op: (B, A) => B): B
...
```
{% endtab %}

{% endtabs %}

`foldLeft` applies a two-parameter function `op` to an initial value `z` and all elements of this collection, going left to right. Shown below is an example of its usage.

Starting with an initial value of 0, `foldLeft` here applies the function `(m, n) => m + n` to each element in the List and the previous accumulated value.

{% tabs foldLeft_use %}

{% tab 'Scala 2 and 3' for=foldLeft_use %}
```scala mdoc
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val res = numbers.foldLeft(0)((m, n) => m + n)
println(res) // 55
```
{% endtab %}

{% endtabs %}

### Use cases

Expand All @@ -43,29 +64,53 @@ Suggested use cases for multiple parameter lists include:
It so happens that in Scala, type inference proceeds one parameter list at a time.
Say you have the following method:

{% tabs foldLeft1_definition %}

{% tab 'Scala 2 and 3' for=foldLeft1_definition %}
```scala mdoc
def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ???
```
{% endtab %}

{% endtabs %}

Then you'd like to call it in the following way, but will find that it doesn't compile:

{% tabs foldLeft1_wrong_use %}

{% tab 'Scala 2 and 3' for=foldLeft1_wrong_use %}
```scala mdoc:fail
def notPossible = foldLeft1(numbers, 0, _ + _)
```
{% endtab %}

{% endtabs %}

you will have to call it like one of the below ways:

{% tabs foldLeft1_good_use %}

{% tab 'Scala 2 and 3' for=foldLeft1_good_use %}
```scala mdoc
def firstWay = foldLeft1[Int, Int](numbers, 0, _ + _)
def secondWay = foldLeft1(numbers, 0, (a: Int, b: Int) => a + b)
```
{% endtab %}

{% endtabs %}

That's because Scala won't be able to infer the type of the function `_ + _`, as it's still inferring `A` and `B`. By moving the parameter `op` to its own parameter list, `A` and `B` are inferred in the first parameter list. These inferred types will then be available to the second parameter list and `_ + _` will match the inferred type `(Int, Int) => Int`

{% tabs foldLeft2_definition_and_use %}

{% tab 'Scala 2 and 3' for=foldLeft2_definition_and_use %}
```scala mdoc
def foldLeft2[A, B](as: List[A], b0: B)(op: (B, A) => B) = ???
def possible = foldLeft2(numbers, 0)(_ + _)
```
{% endtab %}

{% endtabs %}

This definition doesn't need any type hints and can infer all of its type parameters.

Expand All @@ -76,16 +121,31 @@ To specify only certain parameters as [`implicit`](https://docs.scala-lang.org/t

An example of this is:

{% tabs execute_definition class=tabs-scala-version %}

{% tab 'Scala 2' for=execute_definition %}
```scala mdoc
def execute(arg: Int)(implicit ec: scala.concurrent.ExecutionContext) = ???
```
{% endtab %}

{% tab 'Scala 3' for=execute_definition %}
```scala
def execute(arg: Int)(using ec: scala.concurrent.ExecutionContext) = ???
```
{% endtab %}

{% endtabs %}

#### Partial application

When a method is called with a fewer number of parameter lists, then this will yield a function taking the missing parameter lists as its arguments. This is formally known as [partial application](https://en.wikipedia.org/wiki/Partial_application).

For example,

{% tabs foldLeft_partial %}

{% tab 'Scala 2 and 3' for=foldLeft_partial %}
```scala mdoc:nest
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val numberFunc = numbers.foldLeft(List[Int]()) _
Expand All @@ -96,6 +156,9 @@ println(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
val cubes = numberFunc((xs, x) => xs :+ x*x*x)
println(cubes) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000)
```
{% endtab %}

{% endtabs %}

### Comparison with "currying"

Expand All @@ -122,6 +185,9 @@ multiple parameter lists and currying. Though they are different at
the definition site, the call site might nonetheless look identical,
as in this example:

{% tabs about_currying %}

{% tab 'Scala 2 and 3' for=about_currying %}
```scala mdoc
// version with multiple parameter lists
def addMultiple(n1: Int)(n2: Int) = n1 + n2
Expand All @@ -134,3 +200,6 @@ addMultiple(3)(4) // 7
addCurried1(3)(4) // 7
addCurried2(3)(4) // 7
```
{% endtab %}

{% endtabs %}