1

I'm making a function that takes a lambda, and uses .tupled on it if possible (arity 2+). For the compiler to allow using that, it needs to know if the lambda is really a Function2 (~ Function22). However, pattern-matching for Function2[Any,Any,Any] means I'm left with a (Any, Any) => Any rather than its original types. Not even knowing the arity isn't helping either. I tried matching like case f[A <: Any, B <: Any, C <: Any]: scala.Function2[A, B, C] => f.tupled in an attempt to preserve types, but it doesn't really allow type parameters on a case.

The code:

val add = (a: Int, b: Int) => a + b
add: (Int, Int) => Int = <function2>

val tpl = (fn: Any) => {
  fn match {
    case f: Function0[Any] => f
    case f: Function1[Any,Any] => f
    case f: Function2[Any,Any,Any] => f.tupled
//  case f: Function3[Any,Any,Any,Any] => f.tupled
//  ...
//  case _ => { throw new Exception("huh") }
  }
}

// actual result:
tpl(add)
res0: Any = <function1>

// desired result is like this one:
scala> add.tupled
res3: ((Int, Int)) => Int = <function1>

Bonus points if I won't need pattern-matching cases for each possible level of arity...

Kiara Grouwstra
  • 5,723
  • 4
  • 21
  • 36
  • 1
    Can't be done. What would the return type even be? `Function1[(A, B)]`? `Function1[(A, B, C)]`? `Function[(A, B, C, D)]`? – dcastro Sep 07 '15 at 09:10
  • 1
    I believe that you need dependent types to avoid handling every arity separately. You might have to switch to [Agda](https://en.wikipedia.org/wiki/Agda_%28programming_language%29) or [Idris](http://docs.idris-lang.org/en/latest/) or something similar. – Lii Sep 07 '15 at 09:11
  • @dcastro: good point. I suppose I've been more familiar with dynamically typed languages, and have been having trouble with the gap between how types can turn out in practice vs. the 'greatest common denominator' guarantees the compiler can make about them... – Kiara Grouwstra Sep 07 '15 at 09:27
  • @Lii: thanks for the recommendations; I'll have to remember those. For the moment I'm hoping not to complicate my project even further yet though... – Kiara Grouwstra Sep 07 '15 at 09:30

1 Answers1

1

The answer is ugly, as you may have expected. Using a pattern-match on a function as a val won't work. When you start out with Any you've already lost a ton of type information. And the standard library isn't really helping either, as there is no abstraction over arity of functions. That means that we can't even really use reflection to try to grab the type parameters, because we don't even know how many there are. You can figure out what FunctionN you have, but not it's contained types, as they are already lost at this point.

Another possibility is to make tpl a method and overload it for each FunctionN.

def tpl[A](f: Function0[A]): Function0[A] = f
def tpl[A, R](f: Function1[A, R]): Function1[A, R] = f
def tpl[A1, A2, R](f: Function2[A1, A2, R]): Function1[(A1, A2), R] = f.tupled
def tpl[A1, A2, A3, R](f: Function3[A1, A2, A3, R]): Function1[(A1, A2, A3), R] = f.tupled
// ... and so on

scala> val add = (a: Int, b: Int) => a + b
add: (Int, Int) => Int = <function2>

scala> tpl(add)
res0: ((Int, Int)) => Int = <function1>

It's not pretty, but it's at least type-safe. I don't think it would be too difficult to create a macro to generate the overloads 1-22.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • Thanks. It doesn't get super pretty, as you said, but yeah, any bits help. I'm also looking into macros further now -- even if a regular function can't easily do this due to type loss, adding the operations by macro (e.g. `.tupled`) is an interesting alternative. For something that short a macro would seem to have little added value, but obviously this was a simplified version of my function -- for something bigger that might actually elegantly solve things. Thanks for your input; consider this question solved. – Kiara Grouwstra Sep 07 '15 at 15:17