rmi*_*min 22 functional-programming scala static-typing
该Unit
编译器生成的字节码的时候,因为它是类似于得到特殊的处理void
在JVM上.但从概念上讲,作为scala类型系统中的一种类型,它似乎也在语言本身得到特殊处理(下面的例子).
所以我的问题是澄清这一点并理解使用的机制以及是否真的对该Unit
类型进行了特殊处理.
例1:
对于"普通"scala类型Seq
,如果方法返回Seq
,则必须返回Seq
(或更具体的类型Seq
)
def foo1: Seq[Int] = List(1, 2, 3)
def foo2: Seq[Int] = Vector(1, 2, 3)
def foo3: Seq[Int] = "foo" // Fails
Run Code Online (Sandbox Code Playgroud)
前两个例子编译,因为List[Int]
和Vector[Int]
是的亚型Seq[Int]
.第三个失败,因为String
不是.
但是,如果我改变了第三个例子返回Unit
的是,它会编译并没有问题,运行,即便String
是没有的子类型Unit
:
def foo3(): Unit = "foo" // Compiles (with a warning)
Run Code Online (Sandbox Code Playgroud)
我不知道在scala中允许此异常的任何其他类型.那么编译器Unit
对类型系统级别的类型有特殊规则,或者是否存在某种更通用的机制,例如隐式转换.
例2:
我也不清楚单位如何在通常应用方差规则的情况下进行交互.
例如,我们有时会Future[Unit]
在意外使用的地方遇到这个错误,map
而不是flatMap
创建一个Future[Future]
:
def save(customer: Customer): Future[Unit] = ... // Save to database
def foo: Future[Unit] = save(customer1).map(_ => save(customer2))
Run Code Online (Sandbox Code Playgroud)
该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 fails this
Run Code Online (Sandbox Code Playgroud)
对于其他所有非Unit
类型,它将无法编译(除非Any
因为Future[Any]
碰巧是Any
巧合的子类型).
那么编译器在这种情况下是否有特殊规则?或者是否有更一般的过程发生?
Edu*_*bes 13
rethab的回答已经给你链接到规范; 让我补充一点
-Xfatal-warnings
-Ywarn-value-discard
; 对于foo3
编译器警告将提供更多信息discarded non-Unit value
请注意,这个"any to Unit"转换是编译魔术,所以不会-Yno-predef
或-Yno-imports
将禁用它; 你确实需要上面的标志.我认为这是语言规范的一部分错误,好像由于某种原因你想要这个可疑的行为,你可以添加像
implicit def any2Unit(a: Any): Unit = ()
Run Code Online (Sandbox Code Playgroud)
选择退出时需要不支持(根据定义,因为它违反了规范)编译器标志.
我也推荐wartremover,你有这个以及更多.
Mic*_*jac 12
我将回答标题问题以获得更多报道.Unit
在一些地方得到特殊待遇,而不是那些代码示例中发生的事情.在某种程度上,这是因为Unit
编译器的图形减少到void
了JVM.
价值丢弃
这是人们最令人惊讶的情况.根据SLS-6.26.1,只要某个值的预期类型是Unit
,编译器就会Unit
在产生该值的表达式的末尾处理:
如果
ee
具有某种值类型且期望的类型是Unit
,ee
则通过将其嵌入到术语中而将其转换为期望的类型{ ee; () }
.
从而,
def foo3(): Unit = "foo"
Run Code Online (Sandbox Code Playgroud)
变为:
def foo3(): Unit = { "foo" ; () }
Run Code Online (Sandbox Code Playgroud)
同样,
def foo: Future[Unit] = save(customer1).map(_ => save(customer2))
Run Code Online (Sandbox Code Playgroud)
变为:
def foo: Future[Unit] = save(customer1).map(_ => { save(customer2); () })
Run Code Online (Sandbox Code Playgroud)
这样做的好处是,如果您不想,您不需要让方法的最后一个语句具有该类型Unit
.但是,这个好处很小,因为如果返回的方法的最后一个语句Unit
不是a Unit
,那通常表示错误,这就是为什么它有一个警告标志(-Ywarn-value-discard
).
一般来说,如果可能的话,我发现更好地返回更具体的类型,而不是返回Unit
.例如,在保存到数据库时,您可能能够返回保存的值(可能使用新ID或其他内容).
价值等级
Unit
是一个由Scala编译器创建的值类,只有一个实例(如果它需要实例化为一个类).这意味着它会编译void
为JVM上的原语,除非您将其视为一个类(例如().toString
).它在规范中有自己的部分,SLS - 12.2.13.
空块类型
从SLS-6.11开始,假定空块的默认类型为Unit
.例如:
scala> val x = { }
x: Unit = ()
Run Code Online (Sandbox Code Playgroud)
等于
将a Unit
与另一个Unit
(必须是同一个对象,因为只有一个)进行比较时,编译器会发出一个特殊警告,通知你程序中可能存在错误.
scala> ().==(())
<console>:12: warning: comparing values of types Unit and Unit using `==' will always yield true
().==(())
^
res2: Boolean = true
Run Code Online (Sandbox Code Playgroud)
铸件
您可以将任何内容转换为a Unit
,因为编译器会对其进行优化(尽管我不清楚在类型推断后值丢弃是否会接管).
object Test {
val a = "a".asInstanceOf[Unit]
val b = a
}
Run Code Online (Sandbox Code Playgroud)
变为:
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 = ()
}
Run Code Online (Sandbox Code Playgroud)