More help understanding the _ variable

I’ve written the following two functions. Can someone help me understand the error message?

  def mapcar[A1,A2,A3,B](f:(A1,A2,A3)=>B, L1:List[A1],L2:List[A2],L3:List[A3]):List[B] = {
    (L1,L2,L3).zipped.map(f)
  }
  def f (a:Int,b:Int,c:Int) = {a+b+c}

The following works properly, returning List(111, 222, 333, 444)

mapcar(f, List(1,2,3,4), List(10,20,30,40), List(100,200,300,400)) 

However, the following doesn’t work:

mapcar(_+_+_, List(1,2,3,4), List(10,20,30,40), List(100,200,300,400))

giving the following error message

<console>:15: error: missing parameter type for expanded function ((x$1: <error>, x$2, x$3) => x$1.$plus(x$2).$plus(x$3))
       mapcar(_+_+_, List(1,2,3,4), List(10,20,30,40), List(100,200,300,400))

compiler could not figure out the types for f= ++_

I believe because + is overloaded you need to help type inference to figure out what type you want the for the + operation.

The followign should work


> mapcar((_: Int) + (_: Int) + (_: Int), List(1,2,3,4), List(10,20,30,40), List(100,200,300,400))
res1: List[Int] = List(111, 222, 333, 444)

1 Like

Like the others said, the types for _ + _ + _ cannot be inferred here. This is because the type inference works from left to right, with one parameter list at a time.

You can help the compiler to infer types by defining your function differently:

def mapcar[A1,A2,A3,B](L1: List[A1],L2: List[A2],L3: List[A3])(f: (A1,A2,A3)=>B):List[B] = {
  (L1,L2,L3).zipped.map(f)
}

Note that f is defined after the other parameters, and in a separate parameter list (separate parentheses). This way, the compiler will see your lists first and infer the types A1, A2 and A3 from them. As the types are now known when the compiler reaches the second parameter list, the types will no longer be required:

> mapcar(List(1,2,3,4), List(10,20,30,40), List(100,200,300,400))(_ + _ + _) 
res2: List[Int] = List(111, 222, 333, 444)
1 Like

Really interesting that the type inferencer works only from right to left. I’ve probably read that somewhere, but seeing an actual example where it matters makes me understand the principle better.

If I changed the order of the arguments as you (crater2150) suggest, would’t I just be kicking the can down the road? Wouldn’t I encounter the same problem the next time I try to call mapcar with a function whose types are inferable, but with lists whose types are not?

It is much rarer, that the compiler will have to infer the type of a List, as the type of a list, that you can pass around, is usually already known. The only case I can think of right now is when passing empty lists, as any other list’s type is determined by its contents.

The type of lambda on the other hand depends on where you pass it to. The type of the list will not change, if you pass it to another function, but the type of _ + _ + _ depends on the context where you use it. So this way round you’ll have a lot less cases, where explicit types are needed.

You can also see this in the standard library, if a function takes some values and a function as parameter, the function always comes last (e.g. the various fold methods on List)

1 Like