2

How does Scala's method reference work? Suppose there are the following lists:

val list12 = List(1, 2)
val list012 = 0 :: list1 //insert at head
val list123 = list1 :+ 3 //insert at end

Suppose there is a higher order function which accepts an element, a list, and also a function which adds the element to the list and returns the new list:

def append[T](e: T, list: List[T], fun: (List[T], T) => List[T]): List[T] = {
  fun(list, e)
}

If it were to have such a method locally defined:

def appendAtHead[T](list: List[T], e: T): List[T] = {
  e :: list
}

Then the call would be simple:

append(0, list12, appendAtHead[Int])

But how to reference the existing methods :: and :+? The following calls do not work (as they do in Java for example):

append(0, list12, List.::)
append(3, list12, SeqLike.:+) 
Random42
  • 8,989
  • 6
  • 55
  • 86
  • It will not work because `::` and `:+` are instance bound, they do not exist without the instance. Also, the method signature for these functions is just `T => List[T]` – sarveshseri Jul 09 '18 at 09:54

2 Answers2

2

In Scala a method on a class is not equivalent to a free function that takes the class as the first argument. You cannot unbind the object from the method.

Instead, you have to create an anonymous function that calls the method on the object, like this:

append(0, list12, _.::(_))
append(0, list12, _ :+ _)

In your current code List.:: refers to the companion object for the List class, and this does not contain a function called ::.

Tim
  • 26,753
  • 2
  • 16
  • 29
1

Scala doesn't have method references separate from normal lambdas, as Java and Kotlin do (in a case like append(0, list12, appendAtHead[Int]) it does eta-expansion instead, the method name is a short way to write a lambda, as described in Specification 6.26.5).

So use lambdas instead. For :: it's a bit less convenient than for :+, because the arguments are in the wrong order, but then List.:: wouldn't work in Java either (actually, it would because :: is right-associative, which also allows to write _.::(_) in Scala):

append[Int](0, list12, (x, y) => y :: x) // or append(0, list12, (x: List[Int], y: Int) => y :: x)
append[Int](3, list12, _ :+ _) 

or if you help type inference by moving fun into a separate parameter list,

def append[T](e: T, list: List[T])(fun: (List[T], T) => List[T]): List[T] = ...

append(0, list12)((x, y) => y :: x)
append(3, list12)(_ :+ _) 
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Thank you for the answer! Can you please explain (or point me to some reference) as why does moving the function to a separate parameter helps type inference? (there is no need for specifically mentioning `Int`) – Random42 Jul 09 '18 at 11:05
  • 1
    Scala can't infer type parameter `T` from one argument (`0`) and then use it to type another; instead `T` is inferred from all arguments in same list together. With two lists, `T = Int` can be inferred from `0` and `list12`, and then `fun` has a suitable expected type. – Alexey Romanov Jul 09 '18 at 11:53