Scala 編譯器是如何實現Nothing、Null 這種bottom type的?

在Scala 中,Null 和Nothing 是bottom type,他們沒有繼承自其他的類,但卻是任何類的subtype(Null 是所有引用類的bottom type,Nothing 是所有類型包括Null 的subtype)。

但是JVM 是不支持bottom type 的,也就是說如果有個Null 類是其他類的subtype,那麼它必須繼承於其他所有類。

問題是,Scala 編譯器是如何把Null 和Nothing 兩個類編譯到JVM 位元組碼的?


得睡了…先放個鏈接明天再答:

scala.runtime.Nothing$ - Scala Standard Library 2.11.7


typedef decltype(nullptr) nullptr_t;


謝邀,希望能解答你的疑惑:

如下代碼:

object Test {
def a[T](i: T) = i
val b = a _

val c: Nothing = b(null.asInstanceOf[Nothing]) //JVM里null算是bottom type實例
val d = a[Int](c)
}

b的類型是b: (Nothing) =&> Nothing

javap一下:

$ javap Test
Compiled from "Test.scala"
public final class Test {
public static int d();
public static scala.runtime.Nothing$ c();
public static scala.Function1& b();
public static & T a(T);
}

我們看到的是Nothing變成了scala.runtime.Nothing$類型

在Scala源碼里:

package scala.runtime

/**
* Dummy class which exist only to satisfy the JVM. It corresponds
* to `scala.Nothing`. If such type appears in method
* signatures, it is erased to this one.
*/
sealed abstract class Nothing$ extends Throwable

⊥就是在編譯階段推導出來,然後用這種偽類的方式寫入位元組碼

(在Type checking時保證Nothing是所有類的subtype就行了,它只需要保證類型安全)

所以:如果你試圖通過反射什麼的想拿到scala.Nothing這樣的類型,根本取不到,因為不存在,Nothing和Null只存在於編譯期

PS

minimal core calculus for Scala type checking:

http://lampwww.epfl.ch/~odersky/papers/mfcs06.pdf


昨天在翻R大的答案的時候突然想起來自己提的這個問題還坑著,當時和R大簡單交流了一下之後這個問題很快就解決了,今天我來總結一下分享給大家。

當時提這個問題的背景是我正在給我的subset Scala 添加一個JVM 的target(之前只編譯到MIPS 一個平台,做法類似虎書和斯坦福的編譯器課程但多一點優化考慮)。開始的時候的思路是每一個Scala Class 都編譯到到一個Java Class 上去,但想到Null 和Nothing 的時候就懵逼了,其實想通了之後很簡單。

1. Type Checking

其實這一步很好做,在type checking 的時候按規則處理這兩個類型就可以了,typing rules 如下:

2. Code Generation

過了type checking 之後如果要編譯到JVM 位元組碼的話就需要做特殊處理了,因為JVM 位元組碼也有type checking,而JVM 之中是沒有Nothing 和Null 這兩個類型的,所以生成的代碼中不能有Null 和Nothing 的對象。所以就要把Null對象直接編譯為一個null,把Nothing 對象直接編譯為throw。這樣就完成了『類型擦除』。舉個例子,比如方法調用有可能會返回Null 或Nothing(我的編譯器實現中只有某些Native method 會返回Nothing),則它的代碼生成部分如下:

override def visit_dispatch(the_node:Cdispatch,expr:Expression,name:Symbol,actuals:Expressions) : Any = {
expr.accept(this);
val ae = actuals.elements
while(ae.hasNext) {
ae.next.accept(this);
}
val mbind = the_node.mbinding;
val return_type = new ObjectType(symbol_name(mbind.return_type))
val para_type = new ArrayBuffer[Type]();
val pe = mbind.formals.elements
while(pe.hasNext){
para_type += new ObjectType(pe.next.asInstanceOf[Cformal].of_type.name);
}
val static_type = className(mbind.owner)
insts.append(new INVOKEVIRTUAL(constant_pool.addMethodref(
static_type, symbol_name(name), Type.getMethodSignature(return_type, para_type.toArray))));

if(mbind.return_type == "Null) {
insts.append(new POP())
insts.append(new ACONST_NULL())
} else if (mbind.return_type == "Nothing) {
insts.append(new NEW(constant_pool.addClass("java.lang.Error")))
insts.append(new DUP())
insts.append(new PUSH(constant_pool, "Never Return"))
insts.append(new INVOKESPECIAL(constant_pool.addMethodref(
"java.lang.Error", "&", "(Ljava/lang/String;)V")))
insts.append(new ATHROW())
}

}

雖說這兩個類可以被擦除,但我的編譯器在Standard Lib 文件中定義了這兩個類(為了能夠支持JVM以外的其他平台),所以我還是為這兩個類『裝模作樣』地生成了它們的Class (同時也對應了scala.runtime.Nothing$ 這種偽類):

def gen_special_class(name:Symbol, parent:Symbol) : Any = {
val name_string = symbol_name(name);
val parent_string = symbol_name(parent);
class_gen = new ClassGen(name_string,parent_string,
"special class",
ACC_PUBLIC|ACC_ABSTRACT,Array[String]());
if (class_gen.getConstantPool() == null) {
System.out.println("Creating constant pool gen");
class_gen.setConstantPool(new ConstantPoolGen());
} else ();
constant_pool = class_gen.getConstantPool();

val il = new InstructionList()
il.append(new ALOAD(0))
il.append(new INVOKESPECIAL(constant_pool.addMethodref(
parent_string, "&", "()V")))
il.append(new RETURN())
addMethod(new MethodGen(0, Type.VOID, Type.NO_ARGS, Array(), "&", name_string, il, constant_pool))

zout.putNextEntry(new ZipEntry(symbol_name(name).concat(".class")));
var clazz : JavaClass = class_gen.getJavaClass();

clazz.dump(zout);
zout.closeEntry()
};

……
……
gen_special_class(null_sym,any_sym);
gen_special_class(nothing_sym,null_sym);


從某種意義上說Java里null也是bottom type的實例,但沒有一個有名有姓的type來標識它,Scala只是給這個type起了個名字。

JVM里現在的做法其實和Scala完全不一樣,在JVM里的任何Type(除了primitive type)其實都是(DeclaredType | typeof(null)),所以嚴格說起來null不是instance of DeclaredType,而是instance of (DeclareType | typeof(null)),所以你不需要一個bottom type來描述Nullable Type,因為null ISA (Type | typeof(null))。

Scala一方面所有的Type都是Nullable Type,另一方面又想用一個單根體系覆蓋所有類型,所以它才需要這個Nothing作為bottom type,否則null和所有type就不兼容了。

如果你不允許Type是Nullable就不需要這個bottom type了,比如Kotlin里就沒有這個bottom type,類似的還有Swift,在這些語言里定義一個Type並不像JVM里一樣定義的是(DeclaredType | typeof(null)),Type和null不兼容,Type?和null才是兼容的。這樣的問題是你沒法把所有的類型放進一個單根的類樹里,因為typeof(null)不是任何一個類型的父類或子類,同理Type?和Type也沒有派生關係,只是因為Type?其實是(Type | typeof(null))這樣一個algebra type,所以Type ISA Type?,反過來則是一個「不安全」的type cast。

其實我覺得這樣挺好,至少可以消除很多NPE滿天飛的情況。


不實現,曾經有大牛說要寫一個沒有null value 的scalaVM + scala lang,被否,因為沒錢。paper都寫幾份了


編譯器它是bottom type,編譯後。。。他就不是了


推薦閱讀:

printf("%s", NULL) 和 printf("%s
", NULL) 的區別?

怎樣學習 Clojure?
現代編程語言需要具備什麼要素?
int 和 long int 的區別在哪裡?
VR需要掌握什麼編程語言?

TAG:編程語言 | Scala | Java虛擬機JVM | 編譯原理 | 編譯器 |