Rogach
Rogach

Reputation: 27200

Why Unit is a supertype of anything else?

Here's an example:

$ scala
Welcome to Scala 2.11.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_112).
Type in expressions for evaluation. Or try :help.

scala> val a: Unit = 1
<console>:11: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses
       val a: Unit = 1
                     ^
a: Unit = ()

In Scala documentation:

There is only one value of type Unit, ()

Why is Scala compiler silently coercing values to Unit?

A bit of context: I used Future[Unit] type to describe some procedure which does not return anything. And since Future[Unit] is now effectively a subtype of Unit, I got bit by some funny bugs (someFuture.map(a => Future(a)) silently skips calling the operation instead of giving compilation warning). What am I supposed to use as a type of operation that does not return any meaningful result?

Upvotes: 2

Views: 197

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170735

Unit is not a supertype of other types. What happens instead is called value discarding: when the expected type of an expression e is Unit, the compiler replaces it by {e; ()}. This is done to make some behavior more familiar. E.g.

val sb = new StringBuilder
val strings: List[String] = ...
for (str <- strings) { sb.append(str) }

By analogy with for loops in other languages, we would expect it to compile. But without value discarding it wouldn't: this is equivalent to strings.foreach(str => sb.append(str)), the type of str => sb.append(str) is String => StringBuilder (because all append methods on StringBuilder return the builder itself) and foreach on List[String] takes String => Unit.

You can add -Ywarn-value-discard compiler option to warn you when it happens (and write for (sb <- sbOpt) { sb.append("a"); () } explicitly).

Or you can actually go with a trick of defining your own Unit (possibly changing the name to avoid confusion for anyone reading your code):

 object Unit 
 type Unit = Unit.type 

 implicit def unit2scalaunit(a: Unit): scala.Unit = () 
 implicit def scalaunit2unit(a: scala.Unit): Unit = Unit 

This should avoid running into the problem with Future you describe.

Upvotes: 5

Alec
Alec

Reputation: 32309

Unit is not a supertype of everything! Scala actually has a pretty wide variety of conversions that happen automatically and this is one of them. From section 6.26.1 Value Conversions of the Scala language spec, one of the conversions is

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 when you write something like val a: Unit = 1, it gets processed into val a: Unit = { 1; () }, which is quite different. The warning is actually very helpful here - it is warning you that you probably did something wrong: the expression you are trying to put into statement position is pure (has no side-effects), so executing it has no effect (except possibly to cause the program to diverge) on the final output.

Upvotes: 1

Related Questions