sharath chandra sharath chandra - 1 month ago 12
Scala Question

Scala Function Literals Type issue

Why

executeCommand
is accepting the
callBack
function of wrong return type?

import scala.sys.process._

object Test {

def executeCommand(x: String)(y: Int => Unit) = {
def matchStrToInt(str: String) = {
str match {
case "true" => 1
case "false" => 2
}
}
y(matchStrToInt(x))
} //> executeCommand: (x: String)(y: Int => Unit)Unit

executeCommand("true")(callBack)

def callBack(x: Int): Int = {
x
} //> callBack: (x: Int)Int

}


As far as I know Scala is strictly statically typed language.
Can someone explain the reason behind it?

Answer

I think what is happening here is that two separate mechanisms in the Scala compiler are working together:

Value discarding

This is basically just a way to avoid having to explicitly write (), the Unit value, every time Unit is the expected type. Like at the end of a while loop, a Unit returning method, a for(a <- list){ ... } expression, etc.

It rewrites (in memory during compilation, not on disc) code like this

def foo: Unit = 42

to this

def foo: Unit = { 42; () }

Eta expansion

This is how methods in Scala are converted to functions. A method is a JVM construct, an element of a JVM classfile. A function is just a value, an instance of a scala.FunctionN class. You can pass around functions as values (because they are values), but you can't pass around methods as values (because they're not).

So basically the compiler does the following transformation:

def foo(i: Int): Int = i
def bar(f: Int => Int) = f

bar(foo)
 ~~~>
bar( (x1: Int) => foo(x1) )

Bring the two together

So when you have this code:

def foo(i: Int): Int = i
def bar(f: Int => Unit) = f

bar(foo)

The compiler will first use eta expansion to convert foo to a function.

bar( (x1: Int) => foo(x1) )

And then it will see that foo(x1) is an expression of type Int while an expression of type Unit is expected there. So it will apply value discarding.

bar( (x1: Int) => { foo(x1); () } )

You can check that this "problem" only occurs when converting methods to functions:

scala> def bar(f: Int => Unit) = f
bar: (f: Int => Unit)Int => Unit

scala> val foo = (i: Int) => i
foo: Int => Int = <function1>

scala> bar(foo)
<console>:14: error: type mismatch;
 found   : Int => Int
 required: Int => Unit
       bar(foo)
           ^