From d91fa07aebd4bb6f28066b48046058045c53e6c1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 15 Oct 2018 11:04:48 +0200 Subject: [PATCH 1/7] Add spec for auto-tupling of n-ary functions --- .../reference/auto-parameter-tupling-spec.md | 81 +++++++++++++++++++ docs/docs/reference/auto-parameter-tupling.md | 4 +- docs/sidebar.yml | 2 + 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 docs/docs/reference/auto-parameter-tupling-spec.md diff --git a/docs/docs/reference/auto-parameter-tupling-spec.md b/docs/docs/reference/auto-parameter-tupling-spec.md new file mode 100644 index 000000000000..691febe49f84 --- /dev/null +++ b/docs/docs/reference/auto-parameter-tupling-spec.md @@ -0,0 +1,81 @@ +--- +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 also inconvenient. Instead, automatic tupling of function parameters proposed to be able 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 pattern-matching closure using case 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 `F = (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`. + +A type `T` fits a parameter `p` if one of the following two cases is `true`: + +* `p` comes without a type, i.e. it is a simple identifier or `_`. +* `p` is of the form `x: U` or `_: U` and `T` conforms to `U`. + +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 +} +``` + +### Migration + +Code like this could not be written before, hence the new notation would not be ambigouous after addoptation. + +Thought it is possible that someone has written an implicit conversion for `(T1, ..., Tn) => R` into `TupleN[T1, ..., Tn]` +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). diff --git a/docs/docs/reference/auto-parameter-tupling.md b/docs/docs/reference/auto-parameter-tupling.md index fdfdee233ad3..4961d3e59db3 100644 --- a/docs/docs/reference/auto-parameter-tupling.md +++ b/docs/docs/reference/auto-parameter-tupling.md @@ -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). diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 396bd336ed34..74ee57fd5dae 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -53,6 +53,8 @@ sidebar: url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling url: docs/reference/auto-parameter-tupling.html + - title: Auto Parameter Tupling - More Details + url: docs/reference/auto-parameter-tupling-spec.html - title: Named Type Arguments url: docs/reference/named-typeargs.html - title: Erased Terms From 81a4ec0391bd0aa13dfd3cea3a7ca5dbe95763ae Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 15 Oct 2018 11:43:53 +0200 Subject: [PATCH 2/7] Add automatic tupling of function pameters tests --- ...matic-tupling-of-function-parameters.scala | 7 ++++ ...matic-tupling-of-function-parameters.scala | 39 +++++++++++++++++++ ...-tupling-of-function-parameters-spec.check | 3 ++ ...-tupling-of-function-parameters-spec.scala | 18 +++++++++ 4 files changed, 67 insertions(+) create mode 100644 tests/neg/automatic-tupling-of-function-parameters.scala create mode 100644 tests/pos/automatic-tupling-of-function-parameters.scala create mode 100644 tests/run/automatic-tupling-of-function-parameters-spec.check create mode 100644 tests/run/automatic-tupling-of-function-parameters-spec.scala diff --git a/tests/neg/automatic-tupling-of-function-parameters.scala b/tests/neg/automatic-tupling-of-function-parameters.scala new file mode 100644 index 000000000000..c7d01be78856 --- /dev/null +++ b/tests/neg/automatic-tupling-of-function-parameters.scala @@ -0,0 +1,7 @@ + +object Test { + + val f1: Tuple1[Int] => Int = (x: Int) => x // error + val g1: Tuple1[Int] => Int = _ // error + +} diff --git a/tests/pos/automatic-tupling-of-function-parameters.scala b/tests/pos/automatic-tupling-of-function-parameters.scala new file mode 100644 index 000000000000..2afc7eaf10e3 --- /dev/null +++ b/tests/pos/automatic-tupling-of-function-parameters.scala @@ -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 + +} diff --git a/tests/run/automatic-tupling-of-function-parameters-spec.check b/tests/run/automatic-tupling-of-function-parameters-spec.check new file mode 100644 index 000000000000..a0f16194ddb4 --- /dev/null +++ b/tests/run/automatic-tupling-of-function-parameters-spec.check @@ -0,0 +1,3 @@ +List(3, 7, 11) +List(3, 7, 11) +List(3, 7, 11) diff --git a/tests/run/automatic-tupling-of-function-parameters-spec.scala b/tests/run/automatic-tupling-of-function-parameters-spec.scala new file mode 100644 index 000000000000..0ee93420064c --- /dev/null +++ b/tests/run/automatic-tupling-of-function-parameters-spec.scala @@ -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(_ + _)) + } + +} From 70360fc4cb528feecd21566c3e90e9cff7f1a2a8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 15 Oct 2018 14:17:03 +0200 Subject: [PATCH 3/7] Add note on generic tuple support --- docs/docs/reference/auto-parameter-tupling-spec.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/reference/auto-parameter-tupling-spec.md b/docs/docs/reference/auto-parameter-tupling-spec.md index 691febe49f84..a9ed08342740 100644 --- a/docs/docs/reference/auto-parameter-tupling-spec.md +++ b/docs/docs/reference/auto-parameter-tupling-spec.md @@ -67,6 +67,11 @@ is typed as `ProductN[T1, ..., Tn] => Te`, then it will be transformed to } ``` +##### 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 addoptation. From 6436b4bcd79c5e182c00051728718e6ea82a998b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 15 Oct 2018 17:00:23 +0200 Subject: [PATCH 4/7] Add example of implicit conversion that would be overlooked --- ...f-function-parameters-implicit-conversion.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/run/automatic-tupling-of-function-parameters-implicit-conversion.scala diff --git a/tests/run/automatic-tupling-of-function-parameters-implicit-conversion.scala b/tests/run/automatic-tupling-of-function-parameters-implicit-conversion.scala new file mode 100644 index 000000000000..6e963225eb6d --- /dev/null +++ b/tests/run/automatic-tupling-of-function-parameters-implicit-conversion.scala @@ -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)) + } + +} From bac389f0a5b6f069f1e79b0d6bf6b258fe20d4e4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 16 Oct 2018 15:38:21 +0200 Subject: [PATCH 5/7] Address comments --- .../reference/auto-parameter-tupling-spec.md | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/docs/reference/auto-parameter-tupling-spec.md b/docs/docs/reference/auto-parameter-tupling-spec.md index a9ed08342740..4987b0c06929 100644 --- a/docs/docs/reference/auto-parameter-tupling-spec.md +++ b/docs/docs/reference/auto-parameter-tupling-spec.md @@ -18,7 +18,7 @@ xs.map { case (x, y) => x + y } ``` -While correct, this is also inconvenient. Instead, automatic tupling of function parameters proposed to be able to write it the following way: +While correct, this is inconvenient. Instead, we propose to be able to write it the following way: ```scala xs.map { @@ -30,21 +30,20 @@ or, equivalently: xs.map(_ + _) ``` -Generally, a function value with `n > 1` parameters can be converted to a pattern-matching closure using case if the expected type is a unary function type of the form `((T_1, ..., T_n)) => U`. +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 `F = (p1, ..., pn) => E` for `n != 1`, parameters `p1, ..., pn`, and an expression `E`. +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 +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`. +parameter `pi`. Then `f` will conform to the function type `ProductN[T1, ..., Tn] => R`. -A type `T` fits a parameter `p` if one of the following two cases is `true`: +A type `Ti` fits a parameter `pi` if one of the following two cases is `true`: -* `p` comes without a type, i.e. it is a simple identifier or `_`. -* `p` is of the form `x: U` or `_: U` and `T` conforms to `U`. +* `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. @@ -53,7 +52,7 @@ can in turn be adapted to the expected type with auto-tupling. If the a function ```scala -(p1: T1, ..., pn: Tn) => E +(p1: T1, ..., pn: Tn) => e ``` is typed as `ProductN[T1, ..., Tn] => Te`, then it will be transformed to @@ -63,7 +62,7 @@ is typed as `ProductN[T1, ..., Tn] => Te`, then it will be transformed to def p1: T1 = x._1 ... def pn: Tn = x._n - E + e } ``` @@ -74,9 +73,9 @@ Translation of such a tuples would use the `apply` method on the tuple to access ### Migration -Code like this could not be written before, hence the new notation would not be ambigouous after addoptation. +Code like this could not be written before, hence the new notation would not be ambigouous after adoption. -Thought it is possible that someone has written an implicit conversion for `(T1, ..., Tn) => R` into `TupleN[T1, ..., Tn]` +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. From 5feb302c2e659f36b1b523a02f0c1ada352a402e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 16 Oct 2018 16:17:14 +0200 Subject: [PATCH 6/7] Remove from sidebar --- docs/sidebar.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 74ee57fd5dae..396bd336ed34 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -53,8 +53,6 @@ sidebar: url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling url: docs/reference/auto-parameter-tupling.html - - title: Auto Parameter Tupling - More Details - url: docs/reference/auto-parameter-tupling-spec.html - title: Named Type Arguments url: docs/reference/named-typeargs.html - title: Erased Terms From e062c82dccc7429c5e28039418170160f35fd1d4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 16 Oct 2018 16:17:55 +0200 Subject: [PATCH 7/7] Address comments --- docs/docs/reference/auto-parameter-tupling-spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/auto-parameter-tupling-spec.md b/docs/docs/reference/auto-parameter-tupling-spec.md index 4987b0c06929..9381300a23fa 100644 --- a/docs/docs/reference/auto-parameter-tupling-spec.md +++ b/docs/docs/reference/auto-parameter-tupling-spec.md @@ -18,7 +18,7 @@ xs.map { case (x, y) => x + y } ``` -While correct, this is inconvenient. Instead, we propose to be able to write it the following way: +While correct, this is inconvenient. Instead, we propose to write it the following way: ```scala xs.map {