Type inference on anonymous functions with enrich-my-library
Asked Answered
F

3

5

Say I have a method that turns a (function on two elements) into a (function on two sequences):

def seqed[T](f: (T,T) => T): (Seq[T], Seq[T]) => Seq[T] = (_,_).zipped map f

In words, the resulting function takes two sequences xs and ys, and creates a new sequence consisting of (xs(0) f ys(0), xs(1) f ys(1), ...) So, for example, if xss is Seq(Seq(1,2),Seq(3,4)) and f is (a: Int, b: Int) => a + b, we can invoke it thus:

xss reduceLeft seqed(f)         // Seq(4, 6)

or with an anonymous function:

xss reduceLeft seqed[Int](_+_)

This is pretty good; it would be nice to get rid of the [Int] type argument but I don't see how (any ideas?).

To make it feel a bit more like the tupled method, I also tried the enrich-my-library pattern:

class SeqFunction[T](f: (T,T) => T) {
  def seqed: (Seq[T], Seq[T]) => Seq[T] = (_,_).zipped map f
}
implicit def seqFunction[T](f: (T,T) => T) = new SeqFunction(f)

For a pre-defined function this works great, but it's ugly with anonymous ones

xss reduceLeft f.seqed
xss reduceLeft ((_:Int) + (_:Int)).seqed

Is there another way I can reformulate this so that the types are inferred, and I can use syntax something like:

// pseudocode
xss reduceLeft (_+_).seqed         // ... or failing that
xss reduceLeft (_+_).seqed[Int]

? Or am I asking too much of type inference?

Fauna answered 30/11, 2011 at 15:48 Comment(1)
Here Daniel Spiewak made a presentation about type systems and type inference in scala and other static typed languages. Maybe that's not exactly on topic, but anyway, I found it extremely interesting.Jews
F
0

The reason why a type annotation is required in

xss reduceLeft seqed[Int](_+_)

but not in

xs zip ys map Function.tupled(_+_)

is due to the difference in type requirements between map and reduceLeft.

def reduceLeft [B >: A] (f: (B, A) ⇒ B): B 
def map        [B]      (f: (A) ⇒ B): Seq[B]   // simple version!

reduceLeft expects seqed to return type B where B >: Int. It seems that therefore the precise type for seqed cannot be known, so we have to provide the annotation. More info in this question.

One way to overcome this is to re-implement reduceLeft without the lower bound.

implicit def withReduceL[T](xs: Seq[T]) = new {
  def reduceL(f: (T, T) => T) = xs reduceLeft f
}

Test:

scala> Seq(Seq(1,2,3), Seq(2,2,2)) reduceL seqed(_+_)
res1: Seq[Int] = List(3, 4, 5)

The problem now is that this now doesn't work on subtypes of Seq (e.g. List), with or without the [Int] parameter:

scala> Seq(List(1,2,3), List(2,2,2)) reduceL seqed(_+_)
<console>:11: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
              Seq(List(1,2,3), List(2,2,2)) reduceL seqed(_+_)
                                                          ^

reduceL expects a function of type (List[Int], List[Int]) => List[Int]. Because Function2 is defined as Function2 [-T1, -T2, +R], (Seq[Int], Seq[Int]) => Seq[Int] is not a valid substitution.

Fauna answered 4/12, 2011 at 3:36 Comment(0)
K
5

You can't do it the way you want, but look at Function.tupled, which is a counter-part to .tupled that solves this very same problem.

scala> List(1, 2, 3) zip List(1, 2, 3) map (_ + _).tupled
<console>:8: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
              List(1, 2, 3) zip List(1, 2, 3) map (_ + _).tupled
                                                   ^
<console>:8: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))
              List(1, 2, 3) zip List(1, 2, 3) map (_ + _).tupled
                                                       ^

scala> List(1, 2, 3) zip List(1, 2, 3) map Function.tupled(_ + _)
res7: List[Int] = List(2, 4, 6)
Katushka answered 30/11, 2011 at 21:24 Comment(1)
The signature of Function.tupled is def tupled[a1, a2, b](f: (a1, a2) => b): Tuple2[a1, a2] => b. What I can't work out how it knows the parameter types in the above example, whereas my method requires the [Int] in xss reduceLeft seqed[Int](_+_).Fauna
U
4

I am pretty sure you are asking too much. Type inference in Scala goes from left to right, so the type of (_+_) needs to be figured out first before even considering the .sedeq part. And there isn't enough information there.

Uxorious answered 30/11, 2011 at 18:36 Comment(0)
F
0

The reason why a type annotation is required in

xss reduceLeft seqed[Int](_+_)

but not in

xs zip ys map Function.tupled(_+_)

is due to the difference in type requirements between map and reduceLeft.

def reduceLeft [B >: A] (f: (B, A) ⇒ B): B 
def map        [B]      (f: (A) ⇒ B): Seq[B]   // simple version!

reduceLeft expects seqed to return type B where B >: Int. It seems that therefore the precise type for seqed cannot be known, so we have to provide the annotation. More info in this question.

One way to overcome this is to re-implement reduceLeft without the lower bound.

implicit def withReduceL[T](xs: Seq[T]) = new {
  def reduceL(f: (T, T) => T) = xs reduceLeft f
}

Test:

scala> Seq(Seq(1,2,3), Seq(2,2,2)) reduceL seqed(_+_)
res1: Seq[Int] = List(3, 4, 5)

The problem now is that this now doesn't work on subtypes of Seq (e.g. List), with or without the [Int] parameter:

scala> Seq(List(1,2,3), List(2,2,2)) reduceL seqed(_+_)
<console>:11: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
              Seq(List(1,2,3), List(2,2,2)) reduceL seqed(_+_)
                                                          ^

reduceL expects a function of type (List[Int], List[Int]) => List[Int]. Because Function2 is defined as Function2 [-T1, -T2, +R], (Seq[Int], Seq[Int]) => Seq[Int] is not a valid substitution.

Fauna answered 4/12, 2011 at 3:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.