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

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

Closed
odersky opened this Issue Oct 31, 2015 · 15 comments

Comments

Projects
None yet
4 participants
@odersky
Contributor

odersky commented Oct 31, 2015

Add the following automatic conversion:

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

@retronym

This comment has been minimized.

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)
@retronym

This comment has been minimized.

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.
@odersky

This comment has been minimized.

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.

@SethTisue

This comment has been minimized.

Member

SethTisue commented Nov 1, 2015

new proto-SIP, attention @dickwall!

@retronym

This comment has been minimized.

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)
<console>: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)
                               ^
<console>:13: error: missing parameter type
       "abc".zipWithIndex.map((x, i) => 42)
                                  ^
@retronym

This comment has been minimized.

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)
<console>: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)
@retronym

This comment has been minimized.

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?

@retronym

This comment has been minimized.

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.
@retronym

This comment has been minimized.

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
@retronym

This comment has been minimized.

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

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.
@nafg

This comment has been minimized.

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)

@SethTisue SethTisue changed the title from SIP: Auto-uncurry n-ary functions. to SIP: Auto-tupling of n-ary functions. Jan 11, 2016

@SethTisue

This comment has been minimized.

Member

SethTisue commented Jan 11, 2016

(I've updated the ticket name.)

@odersky

This comment has been minimized.

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.

@odersky

This comment has been minimized.

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

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.

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.

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.
@odersky

This comment has been minimized.

Contributor

odersky commented Feb 16, 2016

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment