在类型系统中,scala编译器对单元类型有什么特殊的规则 [英] What special rules does the scala compiler have for the unit type within the type system
问题描述
Unit
得到编译器的特殊处理,因为它类似于jvm上的 void
。但在概念上作为scala类型系统中的一种类型,似乎它也在语言本身中得到了特殊的处理(下面的例子)。所以我的问题是关于澄清这个问题并了解使用了哪些机制以及是否对 Unit
类型有特殊待遇。
示例1
对于正常scala类型,如 Seq $ c如果一个方法返回
) Seq
,那么你必须返回 Seq
(或者一个更具体的类型, code> Seq
def foo1:Seq [Int] = List(1, 2,3)
$ b
def foo2:Seq [Int] = Vector(1,2,3)
def foo3:Seq [Int] =foo//失败
前两个例子编译是因为
List [Int]
和Vector [Int]
是Seq [Int]
的子类型。第三个失败,因为String
不是。
但是如果我改变第三个例子返回
Unit
尽管String
不是 / em>Unit
的子类型:def foo3(): Unit =foo//编译(带有警告)
我不知道任何其他类型,这个例外将被允许在scala中。因此,编译器在类型系统级别对
Unit
类型有特殊的规则,或者在工作中有某种更通用的机制,隐式转换。
示例2:
我也不清楚单位如何在通常应用差异规则的情况下进行交互。
例如,我们有时会使用Future [单元]
我们意外地使用map
而不是flatMap
和创建Future [Future]
:
def save(customer:Customer ):Future [Unit] = ... //保存到数据库
def foo:Future [Unit] = save(customer1).map(_ => save(customer2))
map 正在创建一个 Future [Future [Unit]]
,编译器需要一个Future [Unit]
。然后这个编译!
起初我以为这是因为
Future [+ T]
是协变的,但实际上Future [Unit]
不是Unit
的子类型,所以它似乎不是那个。
例如,如果类型变为
Boolean
,编译器检测到这个错误:def save(customer:Customer):Future [Boolean] = ...
def foo:Future [Boolean] = save(customer1) .map(_ => save(customer2))// Compiler failed this
非 -
Unit
类型将不会编译(除任何
因为未来[Any]
碰巧是巧合的任何
的子类型)。
编译器也有这种情况下的特殊规则?或者是否有一个更一般的过程发生?
解决方案我将回答题目问题以获得更多报道。
Unit
在几个地方得到了特殊待遇,比这些代码示例中的更多。部分原因是这是因为Unit
是编译器的一个参数,它在JVM上简化为void
。
取消价值
这是对人们来说最令人惊讶的情况。任何时候某个值的预期类型都是
Unit
,编译器会在生成的表达式结尾处贴上Unit
该值根据 SLS - 6.26。 1 :
如果
ee
有一些值类型,类型为单元
,ee
通过将其嵌入术语{ EE; ()}
。
因此,
def foo3():Unit =foo
变成:
def foo3():Unit = {foo; ()}
同样,
def foo:Future [Unit] = save(customer1).map(_ => save(customer2))
$ b 成为:
def foo:Future [单位] =保存(客户1)。 map(_ => {save(customer2);()})
这是你不需要让方法的最后一个语句的类型为
Unit
,如果你不想。但是,这种好处很小,因为如果返回Unit
的方法的最后一个语句不是Unit
,那么 通常表示一个错误,这就是为什么它有一个警告标志(-Ywarn-value-discard
)。
一般而言,如果可能的话,我发现最好返回一个更具体的类型,而不是返回
Unit
。例如,当保存到数据库时,您可能会返回保存的值(可能带有新的ID或其他内容)。
值类
$ b
单元
是由Scala编译器,只有一个实例(如果它需要实例化为一个类)。这意味着它可以编译到JVM上的基元void
,除非您将它视为一个类(例如()。toString
)。它在规范中有它自己的部分, SLS - 12.2.13 。
空块类型
从 SLS - 6.11 ,空块的默认类型假定为
Unit
。例如:
scala> val x = {}
x:Unit =()
等于
比较
单位
与另一个Unit
(它必须是同一个对象,因为只有一个),编译器会发出一个特殊的警告,告诉你程序中可能有错误。scala> ()。==(())
< console>:12:warning:使用'=='比较类型Unit和Unit的值总是会产生真实的
()。==(() )
$
res2:Boolean = true
投射
您可以将任何内容投射到
Unit
,as编译器会优化它(尽管我不清楚在类型推断后值丢弃是否被接管)。
object Test {
val a =a.asInstanceOf [单位]
val b = a
}
变成:
object Test extends Object {
def< init>():Test .type = {
Test.super。< init>();
()
};
private [this] val a:scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT;
< stable> <存取> def a():Unit =();
private [this] val b:scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT;
< stable> <存取> def b():Unit =()
}
The
Unit
gets special handling by the compiler when generating byte code because it's analogous tovoid
on the jvm. But conceptually as a type within the scala type system, it seems like it also gets special treatment in the language itself (examples below).So my question is about clarifying this and understanding what mechanisms are used and if there really is special treatment for the
Unit
type.
Example 1:
For "normal" scala types like
Seq
, if a method returnsSeq
, then you must returnSeq
(or a more specific type that extendsSeq
)def foo1: Seq[Int] = List(1, 2, 3) def foo2: Seq[Int] = Vector(1, 2, 3) def foo3: Seq[Int] = "foo" // Fails
The first two examples compile because
List[Int]
andVector[Int]
are subtypes ofSeq[Int]
. The third one fails becauseString
isn't.But if I change the third example to return
Unit
though, it will compile and run without issue even thoughString
isn't a subtype ofUnit
:def foo3(): Unit = "foo" // Compiles (with a warning)
I don't know of any other type which this exception would be allowed for in scala. So does the compiler have special rules for the
Unit
type at the type system level, or is there some kind of more general mechanism at work e.g. an implicit conversion.
Example 2:
I'm also not clear how unit interacts in situations where variance rules would normally be applied.
For example, we sometimes hit this bug with
Future[Unit]
where we accidentally usemap
instead offlatMap
and create aFuture[Future]
:def save(customer: Customer): Future[Unit] = ... // Save to database def foo: Future[Unit] = save(customer1).map(_ => save(customer2))
The
map
is creating aFuture[Future[Unit]]
and the compiler requires aFuture[Unit]
. Yet this compiles!At first I thought this was because
Future[+T]
is covariant, but actuallyFuture[Unit]
isn't a subtype ofUnit
so it doesn't seem to be that.If the type gets changed to
Boolean
for example, the compiler detects the bug:def save(customer: Customer): Future[Boolean] = ... def foo: Future[Boolean] = save(customer1).map(_ => save(customer2)) // Compiler fails this
And for every other non-
Unit
type it won't compile (exceptAny
becauseFuture[Any]
happens to be a subtype ofAny
by coincidence).So does the compiler have special rules in this case? Or is there a more general process happening?
解决方案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 becauseUnit
is a figment of the compiler that reduces tovoid
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 onUnit
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 isUnit
,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 returnsUnit
isn't aUnit
, 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 primitivevoid
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 anotherUnit
(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 = () }
这篇关于在类型系统中,scala编译器对单元类型有什么特殊的规则的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!