Skip to content

Add spec for auto-tupling of n-ary functions #5255

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 7 commits into from
Oct 18, 2018
Merged
Show file tree
Hide file tree
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
85 changes: 85 additions & 0 deletions docs/docs/reference/auto-parameter-tupling-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
layout: doc-page
title: "Automatic Tupling of Function Parameters - More Details"
---

### Motivation

Say you have a list of pairs

```scala
val xs: List[(Int, Int)]
```

and you want to map `xs` to a list of `Int`s so that each pair of numbers is mapped to their sum.
Previously, the best way to do this was with a pattern-matching decomposition:
```scala
xs.map {
case (x, y) => x + y
}
```
While correct, this is inconvenient. Instead, we propose to write it the following way:

```scala
xs.map {
(x, y) => x + y
}
```
or, equivalently:
```scala
xs.map(_ + _)
```

Generally, a function value with `n > 1` parameters can be converted to a function with tupled arguments if the expected type is a unary function type of the form `((T_1, ..., T_n)) => U`.

### Type Checking

Let a function `f` of the form `(p1, ..., pn) => e` for `n != 1`, parameters `p1, ..., pn`, and an expression `e`.

If the expected type of `f` is a fully defined function type or SAM-type that has a
single parameter of a subtype of `ProductN[T1, ..., Tn]`, where each type `Ti` fits the corresponding
parameter `pi`. Then `f` will conform to the function type `ProductN[T1, ..., Tn] => R`.

A type `Ti` fits a parameter `pi` if one of the following two cases is `true`:

* `pi` comes without a type, i.e. it is a simple identifier or `_`.
* `pi` is of the form `x: Ui` or `_: Ui` and `Ti` conforms to `Ui`.

Auto-tupling composes with eta-expansion. That is an n-ary function generated by eta-expansion
can in turn be adapted to the expected type with auto-tupling.

#### Term addaptation

If the a function
```scala
(p1: T1, ..., pn: Tn) => e
```

is typed as `ProductN[T1, ..., Tn] => Te`, then it will be transformed to

```scala
(x: TupleN[T1, ..., Tn]) => {
def p1: T1 = x._1
...
def pn: Tn = x._n
e
}
```

##### Generic tuples

If we come to support generic tuples, which provide the possibility of having tuples/functions of arities larger than 22 we would need to additionally support generic tuples of the form `T1 *: T2 *: ...`.
Translation of such a tuples would use the `apply` method on the tuple to access the elements instead of the `_N` methods of `Product`.

### Migration

Code like this could not be written before, hence the new notation would not be ambigouous after adoption.

Though it is possible that someone has written an implicit conversion form `(T1, ..., Tn) => R` to `TupleN[T1, ..., Tn] => R`
for some `n`. This change could be detected and fixed by `Scalafix`. Furthermore, such conversion would probably
be doing the same translation (semantically) but in a less efficient way.

### Reference

For more info see:
* [Issue #897](https://github.com/lampepfl/dotty/issues/897).
4 changes: 3 additions & 1 deletion docs/docs/reference/auto-parameter-tupling.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ function type of the form `((T_1, ..., T_n)) => U`.

### Reference

For more info, see [Issue #897](https://github.com/lampepfl/dotty/issues/897).
For more info see:
* [More details](./auto-parameter-tupling-spec.html)
* [Issue #897](https://github.com/lampepfl/dotty/issues/897).
7 changes: 7 additions & 0 deletions tests/neg/automatic-tupling-of-function-parameters.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

object Test {

val f1: Tuple1[Int] => Int = (x: Int) => x // error
val g1: Tuple1[Int] => Int = _ // error

}
39 changes: 39 additions & 0 deletions tests/pos/automatic-tupling-of-function-parameters.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

object Test {

val f2: Tuple2[Int, String] => Int = (x, y) => x
val g2: Tuple2[Int, String] => Int = _ + _.length

// FIXME issue #5257
// val h2: Int *: Int *: Unit => Int = (x, y) => x + y
// val k2: Int *: Tuple1[Int] => Int = (x, y) => x + y

type T2 = Tuple2[Int, Int]
val h2: T2 => Int = (x1, x2) => 2

val f3: Tuple3[Int, Int, Int] => Int = (x1, x2, x3) => 3
val g3: Tuple3[Int, Int, Int] => Int = _ + _ + _

val f5: Tuple5[Int, Int, Int, Int, Int] => Int = (x1, x2, x3, x4, x5) => 5
val g5: Tuple5[Int, Int, Int, Int, Int] => Int = _ + _ + _ + _ + _

val f10: Tuple10[Int, Int, Int, Int, Int, Int, Int, Int, Int, Int] => Int =
(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => 10
val g10: Tuple10[Int, Int, Int, Int, Int, Int, Int, Int, Int, Int] => Int =
_ + _ + _ + _ + _ + _ + _ + _ + _ + _

val f22: Tuple22[Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int] => Int =
(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) => 22
val g22: Tuple22[Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int] => Int =
_ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _

// FIMXE Tuples of size larger that 22 are not supported yet (issue #5256)
// val f23: ((Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)) => Int =
// (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) => 22
// val g23: ((Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)) => Int =
// _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _

// type T23 = (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
// val h23: T23 => Int = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) => 23

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

object Test {

implicit def F2ToT2(f: Function2[Int, Int, Int]): Tuple2[Int, Int] => Int = {
???
x => f(x._1, x._2)
}

def main(args: Array[String]): Unit = {
val f: Tuple2[Int, Int] => Int = (x: Int, y: Int) => x + y
f((3, 4))
}

}
3 changes: 3 additions & 0 deletions tests/run/automatic-tupling-of-function-parameters-spec.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
List(3, 7, 11)
List(3, 7, 11)
List(3, 7, 11)
18 changes: 18 additions & 0 deletions tests/run/automatic-tupling-of-function-parameters-spec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

object Test {

def main(args: Array[String]): Unit = {
val xs: List[(Int, Int)] = (1, 2) :: (3, 4) :: (5, 6) :: Nil

println(xs.map {
case (x, y) => x + y
})

println(xs.map {
(x, y) => x + y
})

println(xs.map(_ + _))
}

}