# SIP: Auto-tupling of n-ary functions. #897

Closed
opened this Issue Oct 31, 2015 · 15 comments

Projects
None yet
4 participants
Contributor

### odersky commented Oct 31, 2015

Let

``````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`, then `F` is rewritten to

``````x => {
def p1 = x._1
...
def pn = x._n
E
}
``````

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

1. `p` comes without a type, i.e. it is a simple identifier or `_`.
2. `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.

## Examples:

``````val pairs = List(1, 2, 3).zipWithIndex
pairs.map(_ + _)

def plus(x: Int, y: Int) = x + y
pairs.map(plus)
``````

### odersky added a commit to dotty-staging/dotty that referenced this issue Oct 31, 2015

``` Auto-uncurry n-ary functions. ```
`Implements SIP lampepfl#897.`
``` f07fcd8 ```

Merged

Contributor

### retronym commented Nov 1, 2015

 What's the planned interaction with eta expansion? Would the following typecheck? ```def foo(a: Any, b: Any) = 0 List(1, 2, 3).zipWithIndex.map(foo)```
Contributor

### retronym commented Nov 1, 2015

 Function literals currently influence overload resolution (their parameter types are taken as part of the "shape type"). Overload resolution might then filter out alternatives that would otherwise have been satisfied by this feature. For example: ```def foo(a: Tuple2[Int, Int] => String) def foo(a: Any => String) foo((a, b) => a + b)``` (I think this feature is still might be a nett win, just seeking out any unfortunate feature interactions to consider.)

### odersky added a commit to dotty-staging/dotty that referenced this issue Nov 1, 2015

``` Add more pos and neg tests ```
`Tests suggested by @retronym's comments on issue lampepfl#897.`
``` 0b87375 ```
Contributor

### odersky commented Nov 1, 2015

 @retronym. Eta expansion: Yes, the two should be combinable. I added text to the description to say so. Overloading resolution: Yes, that's unfortunate. But not new. The same problem arises with implicit conversions.
Member

### SethTisue commented Nov 1, 2015

 new proto-SIP, attention @dickwall!
Contributor

### retronym commented Nov 2, 2015

 I guess it's also worth mentioning that this will kick in before implicits, which has the potential to change the meaning of existing code that currently use implicits to achieve the same sort of result. One migration technique here would be to have a compiler flag that emits a warning when this feature is triggered. One could use this when compiling existing sources with the new compiler. One might also ask: Why add a language feature if implicits could serve the same role today? The answer is that the solution using implicits requires the anon function parameter types to be explicitly provided. ```scala> implicit def Function2Tupled[A, B, C](f: (A, B) => C): ((A, B)) => C = f.tupled warning: there was one feature warning; re-run with -feature for details Function2Tupled: [A, B, C](f: (A, B) => C)((A, B)) => C scala> "abc".zipWithIndex.map((x: Char, i: Int) => 42) res2: scala.collection.immutable.IndexedSeq[Int] = Vector(42, 42, 42) scala> "abc".zipWithIndex.map((x, i) => 42) :13: error: missing parameter type Note: The expected type requires a one-argument function accepting a 2-Tuple. Consider a pattern matching anonymous function, `{ case (x, i) => ... }` "abc".zipWithIndex.map((x, i) => 42) ^ :13: error: missing parameter type "abc".zipWithIndex.map((x, i) => 42) ^```
Contributor

### retronym commented Nov 2, 2015

 I wonder if `compatibility` is too lenient. We don't use the same notion for functions that match the arity: ```scala> implicit def i2s(i: Int): String = i.toString warning: there was one feature warning; re-run with -feature for details i2s: (i: Int)String scala> ((x: String) => 42 : Int => String) :14: error: type mismatch; found : Int(42) required: Int => String ((x: String) => 42 : Int => String) ^``` Whereas under this proposal, the following would typecheck by composing the implicit conversion with the function: `scala> ((x: String, y: String) => 42 : ((Int, Int)) => String)`
Contributor

### retronym commented Nov 2, 2015

 I find the name of this feature confusing: isn't this about automatically tupling a function, rather than currrying it?
Contributor

### retronym commented Nov 2, 2015

 This desugaring will force by-name function parameters eagerly. ```scala> class T[A] { def foo(f: (=> A) => Int) = f(???) } defined class T scala> new T[(Int, Int)].foo((ii) => 0) res27: Int = 0``` `scala> new T[(Int, Int)].foo((x, y) => 0) // this would throw under the proposed scheme, even though we don't access x or y.`
Contributor

### retronym commented Nov 2, 2015

 Similarly, accessing the product values eagerly would be suprising for mutable products: ```scala> val f = (param: Muple2[Int, Int]) => {val temp1 = param._1; val temp2 = param._2; {m2._1 = -1; temp1}} f: Muple2[Int,Int] => Int = \$\$Lambda\$2763/938970667@479b206b scala> case class Muple2[A, B](var _1: A, var _2: B); val m2 = Muple2(1, 1) defined class Muple2 m2: Muple2[Int,Int] = Muple2(1,1) // proposed desugaring of // val f: Muple2[Int, Int] => Int = (x, y) => m2._1 = -1; x}} scala> val f = (param: Muple2[Int, Int]) => {val temp1 = param._1; val temp2 = param._2; {m2._1 = -1; temp1}} f: Muple2[Int,Int] => Int = \$\$Lambda\$2764/1642844889@54c8a898 scala> f(m2) res44: Int = 1 // expected -1```
Contributor

### retronym commented Nov 2, 2015

 The prototype implementation in dotty allows an expected type of `ProductN`, whereas this proposal seems to limit itself to `TupleN` ("a type of form (T1, ..., Tn)")

### odersky added a commit to dotty-staging/dotty that referenced this issue Nov 17, 2015

``` Auto-uncurry n-ary functions. ```
`Implements SIP lampepfl#897.`
``` 975739a ```

### odersky added a commit to dotty-staging/dotty that referenced this issue Nov 17, 2015

``` Add more pos and neg tests ```
`Tests suggested by @retronym's comments on issue lampepfl#897.`
``` 3dec6ef ```

### nafg commented Jan 10, 2016

 As @retronym said, isn't the name wrong? This doesn't seem related to uncurrying (`(A => B => C) => (A, B) => C`)

Closed

Member

### SethTisue commented Jan 11, 2016

 (I've updated the ticket name.)
Contributor

### odersky commented Feb 16, 2016

 @retronym I wonder if compatibility is too lenient. Good example! I think we should strengthen requirement to "conforms" instead of "is compatible" . That avoids the inconsistency you found.
Contributor

### odersky commented Feb 16, 2016

 @retronym I changed the encoding so that parameters are unpackaged with `def` not `val`.

### odersky added a commit to dotty-staging/dotty that referenced this issue Feb 16, 2016

``` Auto-uncurry n-ary functions. ```
`Implements SIP lampepfl#897.`
``` 29104c9 ```

### odersky added a commit to dotty-staging/dotty that referenced this issue Feb 16, 2016

``` Add more pos and neg tests ```
`Tests suggested by @retronym's comments on issue lampepfl#897.`
``` 62a526e ```

### odersky added a commit to dotty-staging/dotty that referenced this issue Feb 16, 2016

``` Strengthen requirement for auto-tupling ```
```Was: corresponding parameter types "are compatible".
Now: corresponding parameter types "conform".

This avoids the inconsistency mentioned by @retronym in lampepfl#897.```
``` 06bfbd3 ```

### odersky added a commit to dotty-staging/dotty that referenced this issue Feb 16, 2016

``` Untuple using `def` not `val`. ```
`As retronym noted on lampepfl#897, `val` forces to early.`
``` 1729676 ```
Contributor

### odersky commented Feb 16, 2016

 I think all reviewers comments so far are now addressed in the SIP and in #897.

Merged