Search code examples
scalaanonymous-functiontype-inferenceenrich-my-library

Type inference on anonymous functions with enrich-my-library


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?


Solution

  • 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.