What special rules does the scala compiler have for the unit type within the type system

萝らか妹 提交于 2019-12-01 02:12:31

I'm going to answer the title question for more coverage. Unit gets special treatment in a few places, more than what's going on in those code examples. In part, this is because Unit is a figment of the compiler that reduces to void on the JVM.


Value Discarding

This is the most surprising case for people. Any time the expected type of some value is Unit, the compiler tacks on Unit at the end of the expression that produces the value, according to the SLS - 6.26.1:

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

Thus,

def foo3(): Unit = "foo"

becomes:

def foo3(): Unit = { "foo" ; () }

Likewise,

def foo: Future[Unit] = save(customer1).map(_ => save(customer2))

becomes:

def foo: Future[Unit] = save(customer1).map(_ => { save(customer2); () })

The benefit of this is that you don't need to have the last statement of a method have the type Unit if you don't want to. This benefit is small however, because if the last statement of your method that returns Unit isn't a Unit, that usually indicates an error, which is why there is a warning flag for it (-Ywarn-value-discard).

In general, I find it better to return a more specific type, if possible, rather than returning Unit. For example, when saving to a database, you may be able to return the saved value (perhaps with a new ID, or something).


Value Class

Unit is a value class created by the Scala compiler, with only one instance (if it needs to be instantiated as a class at all). This means that it compiles down to the primitive void on the JVM, unless you treat it as a class (e.g. ().toString). It has its very own section in the specification, SLS - 12.2.13.


Empty Block Type

From the SLS - 6.11, the default type of an empty block is assumed to be Unit. For example:

scala> val x = { }
x: Unit = ()

Equals

When comparing a Unit to another Unit (which must be the same object, since there is only one), the compiler will emit a special warning to inform you something is likely wrong in your program.

scala> ().==(())
<console>:12: warning: comparing values of types Unit and Unit using `==' will always yield true
       ().==(())
            ^
res2: Boolean = true

Casting

You can cast anything to a Unit, as the compiler will optimize it away (though it's unclear to me if value discarding takes over after type inference).

object Test {
  val a = "a".asInstanceOf[Unit]
  val b = a
}

becomes:

object Test extends Object {
  def <init>(): Test.type = {
    Test.super.<init>();
    ()
  };
  private[this] val a: scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT;
  <stable> <accessor> def a(): Unit = ();
  private[this] val b: scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT;
  <stable> <accessor> def b(): Unit = ()
}

As written in the scala language specification chapter 6.26.1:

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; () }.

rethab's answer already gave you the link to the spec; just let me add that

  • you can disable this (make the warning an error) through the -Xfatal-warnings compiler flag
  • you'll get better messages with the -Ywarn-value-discard flag; for foo3 the compiler warning will be the more informative discarded non-Unit value

Note that this "any to Unit" conversion is compiler magic, so neither -Yno-predef or -Yno-imports will disable it; you do need the flags above. I consider this being part of the language specification an error, as if for some reason you want this dubious behavior you can just add something like

implicit def any2Unit(a: Any): Unit = ()

while opting-out of it requires an unsupported (by definition, as it breaks the specification) compiler flag.

I also recommend wartremover, where you have this and much more.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!