Search code examples
scalascala-placeholder-syntax

Why does ((_: Int, _: Int) => _ / _) not compile when ((_: Int) / (_: Int)) does?


I am learning Scala and have a very basic question. Consider the following two expressions using the placeholder syntax -

// Syntax A  
val fnA = (_: Int, _: Int) => _ / _

// Syntax B
val fnB = (_: Int) / (_: Int) 

and their attempted applications -

// Syntax A
fnA(33, 3)

// Syntax B
fnB(33, 3)

Out of these two, only B and App(B) are valid syntax and I am not sure why. If the compiler is able to infer the arguments (and the order to apply them in) in fnB why is it not able to do it for fnA? I am basing my question on the premise that fnB is a shorthand for fnA and I am pretty certain there's a flaw in that reasoning. I am just not sure what the flaw is.


Solution

  • The meaning of underscores on the left of arrow =>

    (_: Int, _: Int) => ...
    

    are different from the meaning of underscores around the infix operator /

    (_: Int) / (_: Int) 
    

    The former mean something like

    "We are defining a function that takes two arguments, however we will not use these arguments in the body of the function"

    whilst the latter is a shorthand for defining a function of two arguments

    (x: Int, y: Int) => x / y
    

    For example,

    (_: Int, _: Int) => (_: Int) / (_: Int)
    

    desugars to something like

    (a: Int, b: Int) => ((x: Int, y: Int) => x / y)
    

    where we see arguments a and b are not used in the body which happens to be yet another function ((x: Int, y: Int) => x / y).

    Daniel documents the two meanings as

    _ + _             // Anonymous function placeholder parameter
    _ => 5            // Discarded parameter
    

    As a side-note, consider a somewhat analogous situation at the typelevel involving type constructors and their bounds where the meaning of underscore depends on the context

    CC[_] <: Iterable[_]  
    

    Personally, my mind tricks me to think it is equivalent to

    CC[x] <: Iterable[x]  
    

    however that is not the case, and the meaning of the underscore on the left of <: is different from the meaning on the right

    CC[x] <: Iterable[y] forSome {type y}
    

    Note how x does not appear in Iterable[y] forSome {type y}. Adrian documents this under Common Pitfalls:

    ...perhaps surprisingly, CC[_] <: Traversable[_] is not equivalent to CC[X] <: Traversable[X]. The former expands to CC[X] <: Traversable[T] forSome {type T}, where T is existentially bound and thus unrelated to X.

    and in a comment to type bounds on type constructor

    comparing the level of types and values, it is quite unfortunate that the type-level underscore behaves differently from the value-level underscore. At the value level, _ + _ indeed stands for (x, y) => x + y, a function. As hinted at in my other comment, the type-level underscore is context-sensitive, and it never introduces a type-level function. It's either an anonymous type parameter definition or an anonymous existential. Neither of these have value-level equivalents.

    Hence we should be careful to interpret the meaning of the underscore within its context.