scalascalac

Why does this function parameter need double braces on a tuple?


Note: I am using scalac. Please do not recommend to use sbt instead.

I ran into a peculiar issue that I could resolve, but I am wondering why it works that way and not the way I did it before. Here's a code snippet:

def multiply[A](r1: Vector[A], r2: Vector[A], multOp: (A,A) => A, sumOp: (A, A) => A) = 
    r1.zip(r2).map(multOp).reduce(sumOp)

It does not compile, resulting in an error message like:

Error:(73, 20) type mismatch;
 found   : (A, A) => A
 required: ((A, A)) => ?
    r1.zip(r2).map(multOp).reduce(sumOp)

Changing the snippet to:

def multiply[A](r1: Vector[A], r2: Vector[A], multOp: ((A,A)) => A, sumOp: (A, A) => A) = 
   r1.zip(r2).map(multOp).reduce(sumOp)

will resolve the issue.

Note that sumOp does work with only one pair of braces.

Why?


Solution

  • Method map is defined as taking a single parameter, and (A, A) => A has two. By converting two parameters of type A into one parameter which is a tuple of type (A, A), it compiles.

    (A, A) => A // fails due to two params of type A
    ((A, A)) => A // works due to one param of type (A, A)
    

    On the other hand, reduce is defined as taking two parameters of the same type, so it's happy to take sumOp which matches that description.

    Here are the full signatures found in TraversableLike and TraversableOnce respectively:

    def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
    
    def reduce[A1 >: A](op: (A1, A1) => A1): A1
    

    EDIT (extra info):

    Reason for this is the fact that reduce always takes a 2-arity function (that is, a function of two parameters) in order to reduce the collection to a single value by iteratively applying that function on the result of previous application and the next value. On the other hand, map always takes a 1-arity function and it maps the underlying value with that function. In case of Option, Future, etc. there's only one single underlying value, while in case of a Vector (like yours) there can be many, so it applies it to every element of the collection.

    In some libraries you might come across map2 which takes a two-parameter function. For example, in order to combine two Options (actually, any applicative functors, but let's leave theory aside), you might do:

    // presudocode
    Option(1, 2).map2((a, b) => a + b)
    

    which would give you an Option(3). I think this mechanism has been dropped in the favour of more easily understandable map + product

    // presudocode
    (Option(1) product Option(2)) map ((a, b) => a + b)
    

    Actual scalaz syntax for the line above would be (in Scalaz 7):

    (Option(1) |@| Option(2))((a, b) => a + b)
    

    They are equally powerful principles (what one can do, exactly the same other one can do, no more, no less) so the latter one is usually preferred and sometimes it's the only one provided, but yes, you might come across map2 from time to time.

    Alright, that's a bit of extra info. As far as map is concerned, just remember there's always just one single parameter coming in and one value coming out.