3
votes

I have some trouble with Scala type inference. In following worksheet example I have defined a Map which maps Any values to a function returning a Unit value.

Interestingly, when I try to define the same map using only one line of code it doesn't work because the 'bar' functions return-type suddenly changes to Any instead of Unit.

type UnitFun = (Any) => Unit

val foo = "foo"
val bar = (a: Any) => System.out.println("bar")

val map: Map[Any, UnitFun] = Map().withDefaultValue(((a: Any) => Unit))
val doesCompile: Map[Any, UnitFun] = map + (foo -> bar)

val doesNotCompile: Map[Any, UnitFun] = Map().withDefaultValue(((a: Any) => Unit)) + (foo -> bar)

I am using IDEA14 as IDE with Scala 2.11.6

It seems to me like this is a Feature/Bug of the Scala compiler, or am I missing something?

btw I've just noticed that when I use 'bar' as default value in 'doesNotCompile' like this:

val doesCompileNow: Map[Any, UnitFun] = Map().withDefaultValue(bar) + (foo -> bar)

it suddenly seems to work, I am pretty baffled right now. :D

Edit 1: @Mikolak

In this case, how does the following code works? :)

val a: Any => Unit = (a: Any) => Unit
val b: Any => Unit = (a: Any) => ()

Shouldn't both expressions be of a different type? Or is there some implicit type conversion involved?

1
Added explanation regarding your edit. Note that this is a phenomenon that's completely orthogonal to the original one. - mikołak
Already seen it, I think the implicit conversion from Unit.type to Any (according to intellij output) is what lead me off track on this one. I assume it is just another impicit conversion. :) - pmkrefeld
One thing to add then: do not trust IntelliJ's syntax checking. While slowly getting better, it still has a propensity to output both false positives and false negatives. In other words, your first course of action when you get a weird error should be to run sbt compile. - mikołak
I now know I made a mistake by using 'Unit' instead of '()' but still it is interesting to see that depending on how I write my Code those implicit conversions result in a different type (very first piece of code). ^^ - pmkrefeld
Yeah, that's the price to pay when the language has typing that's both strong and unobtrusive - in corner cases like these you're bound to obtain seemingly unintuitive results. - mikołak

1 Answers

9
votes

Original compile error

The compile error happens because this function:

(a: Any) => Unit

is of type

Any => Unit.type

and not of type:

Any => Unit

In other words, you are returning Unit, the companion object of the Unit type. That companion object has a type Unit.type, which is a different type than Unit (this applies to all companion objects in Scala).

What you need is to actually return a value of type Unit. As outlined in the docs, the only such value is ().

So, your default function should be: (a: Any) => ().


EDIT: Regarding the supplementary question.

Conversion to Unit

Here:

val a: Any => Unit = (a: Any) => Unit

you are explicitly typing the expression to have a return type of Unit. Normally it would result in a type error, but (as you suspect) "fortunately" you triggered one of the pre-defined implicit value conversions, specifically :

Value Discarding

If e has some value type and the expected type is Unit, e is converted to the expected type by embedding it in the term { e; () }.

So, this:

(a: Any) => Unit  //return type is Unit.type

becomes:

(a: Any) => {Unit; ();} //return type is Unit

Note that as per the definition, the conversion applies to any value, so e.g. val c: Unit = "c" produces the same result.