Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Scala type inference fail here?

I have this class in Scala:

object Util {
  class Tapper[A](tapMe: A) {
    def tap(f: A => Unit): A = {
      f(tapMe)
      tapMe
    }

    def tap(fs: (A => Unit)*): A = {
      fs.foreach(_(tapMe))
      tapMe
    }
  }

  implicit def tapper[A](toTap: A): Tapper[A] = new Tapper(toTap)
}

Now,

"aaa".tap(_.trim)

doesn't compile, giving the error

error: missing parameter type for expanded function ((x$1) => x$1.trim)

Why isn't the type inferred as String? From the error it seems that the implicit conversion does fire (otherwise the error would be along the lines of "tap is not a member of class String"). And it seems the conversion must be to Tapper[String], which means the type of the argument is String => Unit (or (String => Unit)*).

The interesting thing is that if I comment out either of tap definitions, then it does compile.

like image 994
Alexey Romanov Avatar asked Jul 23 '10 06:07

Alexey Romanov


People also ask

Does Scala support type inference?

The Scala compiler can infer the types of expressions automatically from contextual information. Therefore, we need not declare the types explicitly. This feature is commonly referred to as type inference. It helps reduce the verbosity of our code, making it more concise and readable.

What would be the type inferred by Scala compiler for variable?

Scala compiler can automatically infer types of each variable declared. If the value of a variable is declared in double-quotes it will automatically be inferred as String. Also, the compiler can infer any value in a single quote is inferred as Char. The compiler, by default it infer any number without decimal as Int.

What does <: mean in Scala?

It means an abstract type member is defined (inside some context, e.g. a trait or class), so that concrete implementations of that context must define that type.


1 Answers

6.26.3 Overloading Resolution

One first determines the set of functions that is potentially applicable based on the shape of the arguments

...

If there is precisely one alternative in B, that alternative is chosen.

Otherwise, let S1, . . . , Sm be the vector of types obtained by typing each argument with an undefined expected type.

Both overloads of tap are potentially applicable (based on the 'shape' of the arguments, which accounts for the arity and type constructors FunctionN).

So the typer proceeds as it would with:

val x = _.trim

and fails.

A smarter algorithm could take the least upper bound of the corresponding parameter type of each alternative, and use this as the expected type. But this complexity isn't really worth it, IMO. Overloading has many corner cases, this is but another.

But there is a trick you can use in this case, if you really need an overload that accepts a single parameter:

object Util {
  class Tapper[A](tapMe: A) {
    def tap(f: A => Unit): A = {
      f(tapMe)
      tapMe
    }

    def tap(f0: A => Unit, f1: A => Unit, fs: (A => Unit)*): A = {
      (Seq(f0, f1) ++ fs).foreach(_(tapMe))
      tapMe
    }
  }

  implicit def tapper[A](toTap: A): Tapper[A] = new Tapper(toTap)

  "".tap(_.toString)
  "".tap(_.toString, _.toString)
  "".tap(_.toString, _.toString, _.toString)
}
like image 73
retronym Avatar answered Oct 11 '22 02:10

retronym