Skip to content

Commit 7e5825a

Browse files
Merge pull request #5255 from dotty-staging/spec-auto-parameter-tupling
Add spec for auto-tupling of n-ary functions
2 parents a821035 + e062c82 commit 7e5825a

7 files changed

+169
-1
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
layout: doc-page
3+
title: "Automatic Tupling of Function Parameters - More Details"
4+
---
5+
6+
### Motivation
7+
8+
Say you have a list of pairs
9+
10+
```scala
11+
val xs: List[(Int, Int)]
12+
```
13+
14+
and you want to map `xs` to a list of `Int`s so that each pair of numbers is mapped to their sum.
15+
Previously, the best way to do this was with a pattern-matching decomposition:
16+
```scala
17+
xs.map {
18+
case (x, y) => x + y
19+
}
20+
```
21+
While correct, this is inconvenient. Instead, we propose to write it the following way:
22+
23+
```scala
24+
xs.map {
25+
(x, y) => x + y
26+
}
27+
```
28+
or, equivalently:
29+
```scala
30+
xs.map(_ + _)
31+
```
32+
33+
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`.
34+
35+
### Type Checking
36+
37+
Let a function `f` of the form `(p1, ..., pn) => e` for `n != 1`, parameters `p1, ..., pn`, and an expression `e`.
38+
39+
If the expected type of `f` is a fully defined function type or SAM-type that has a
40+
single parameter of a subtype of `ProductN[T1, ..., Tn]`, where each type `Ti` fits the corresponding
41+
parameter `pi`. Then `f` will conform to the function type `ProductN[T1, ..., Tn] => R`.
42+
43+
A type `Ti` fits a parameter `pi` if one of the following two cases is `true`:
44+
45+
* `pi` comes without a type, i.e. it is a simple identifier or `_`.
46+
* `pi` is of the form `x: Ui` or `_: Ui` and `Ti` conforms to `Ui`.
47+
48+
Auto-tupling composes with eta-expansion. That is an n-ary function generated by eta-expansion
49+
can in turn be adapted to the expected type with auto-tupling.
50+
51+
#### Term addaptation
52+
53+
If the a function
54+
```scala
55+
(p1: T1, ..., pn: Tn) => e
56+
```
57+
58+
is typed as `ProductN[T1, ..., Tn] => Te`, then it will be transformed to
59+
60+
```scala
61+
(x: TupleN[T1, ..., Tn]) => {
62+
def p1: T1 = x._1
63+
...
64+
def pn: Tn = x._n
65+
e
66+
}
67+
```
68+
69+
##### Generic tuples
70+
71+
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 *: ...`.
72+
Translation of such a tuples would use the `apply` method on the tuple to access the elements instead of the `_N` methods of `Product`.
73+
74+
### Migration
75+
76+
Code like this could not be written before, hence the new notation would not be ambigouous after adoption.
77+
78+
Though it is possible that someone has written an implicit conversion form `(T1, ..., Tn) => R` to `TupleN[T1, ..., Tn] => R`
79+
for some `n`. This change could be detected and fixed by `Scalafix`. Furthermore, such conversion would probably
80+
be doing the same translation (semantically) but in a less efficient way.
81+
82+
### Reference
83+
84+
For more info see:
85+
* [Issue #897](https://github.com/lampepfl/dotty/issues/897).

docs/docs/reference/auto-parameter-tupling.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ function type of the form `((T_1, ..., T_n)) => U`.
3030

3131
### Reference
3232

33-
For more info, see [Issue #897](https://github.com/lampepfl/dotty/issues/897).
33+
For more info see:
34+
* [More details](./auto-parameter-tupling-spec.html)
35+
* [Issue #897](https://github.com/lampepfl/dotty/issues/897).
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
object Test {
3+
4+
val f1: Tuple1[Int] => Int = (x: Int) => x // error
5+
val g1: Tuple1[Int] => Int = _ // error
6+
7+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
object Test {
3+
4+
val f2: Tuple2[Int, String] => Int = (x, y) => x
5+
val g2: Tuple2[Int, String] => Int = _ + _.length
6+
7+
// FIXME issue #5257
8+
// val h2: Int *: Int *: Unit => Int = (x, y) => x + y
9+
// val k2: Int *: Tuple1[Int] => Int = (x, y) => x + y
10+
11+
type T2 = Tuple2[Int, Int]
12+
val h2: T2 => Int = (x1, x2) => 2
13+
14+
val f3: Tuple3[Int, Int, Int] => Int = (x1, x2, x3) => 3
15+
val g3: Tuple3[Int, Int, Int] => Int = _ + _ + _
16+
17+
val f5: Tuple5[Int, Int, Int, Int, Int] => Int = (x1, x2, x3, x4, x5) => 5
18+
val g5: Tuple5[Int, Int, Int, Int, Int] => Int = _ + _ + _ + _ + _
19+
20+
val f10: Tuple10[Int, Int, Int, Int, Int, Int, Int, Int, Int, Int] => Int =
21+
(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => 10
22+
val g10: Tuple10[Int, Int, Int, Int, Int, Int, Int, Int, Int, Int] => Int =
23+
_ + _ + _ + _ + _ + _ + _ + _ + _ + _
24+
25+
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 =
26+
(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) => 22
27+
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 =
28+
_ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _
29+
30+
// FIMXE Tuples of size larger that 22 are not supported yet (issue #5256)
31+
// 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 =
32+
// (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) => 22
33+
// 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 =
34+
// _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _
35+
36+
// 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)
37+
// 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
38+
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
object Test {
3+
4+
implicit def F2ToT2(f: Function2[Int, Int, Int]): Tuple2[Int, Int] => Int = {
5+
???
6+
x => f(x._1, x._2)
7+
}
8+
9+
def main(args: Array[String]): Unit = {
10+
val f: Tuple2[Int, Int] => Int = (x: Int, y: Int) => x + y
11+
f((3, 4))
12+
}
13+
14+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
List(3, 7, 11)
2+
List(3, 7, 11)
3+
List(3, 7, 11)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
object Test {
3+
4+
def main(args: Array[String]): Unit = {
5+
val xs: List[(Int, Int)] = (1, 2) :: (3, 4) :: (5, 6) :: Nil
6+
7+
println(xs.map {
8+
case (x, y) => x + y
9+
})
10+
11+
println(xs.map {
12+
(x, y) => x + y
13+
})
14+
15+
println(xs.map(_ + _))
16+
}
17+
18+
}

0 commit comments

Comments
 (0)