Dottyで何ができるようになるのかとその使い方を簡単にまとめたいと思います. 理論的な部分は深掘りしません.
以下ちょっと長め. 調べきれてない点も多々あるので, 抜けや間違いがあったら教えてください.
そもそもDottyとは
Dependent Object Types (DOT) に基づいた新しいScalaコンパイラです. たまにScala3とか呼ばれたりもしています. DOT計算理論の詳細は省きます (まとめるほど理解できていないとも言う…). 現行のコンパイラと比べ, コンパイラサイズの減少・コンパイル速度向上・様々な機能追加の他, コンパイラ自体の開発安定性も増すパワフルなアップグレードが期待できます. 現行のScalaとの互換性は切れていますが, 勿論重要な機能がなくなるといったことはなく移行ツールも用意されています (詳細後述).
Dottyの新機能
以下では現行のScalaをScala2と表記します.
Union/Intersection Types
Union typesは合併型, Intersection typesは交差型を表しています. Scala2でも with
を使うことで交差型を表現できますが, 順序がなくなり, より正確な交差を表現するようになります.
trait A { type T = Int } trait B { type T = Double } // Scala2の交差型 (A with B) # T //=> Int (B with A) # T //=> Double // Dottyの交差型 (A & B) # T //=> Int & Double // (A & B) と (B & A) は等価 // Scala2と比べて順序がなくなった // Dottyの合併型 class C(x: Int) class D(x: Int) def foo(x: C | D): Int = x.x foo(new C(0)) //=> ok foo(new D(0)) //=> ok val bar = if (cond) 1 else "string" // Scala2 => bar: Any // Dotty => bar: Int | String // このような合併型はScala2では実現できなかった
合併型はパターンマッチ時も特別扱いされ, 漏れがあると警告が出ます.
Improved Type Inference
型推論器「われわれはかしこいので」
一番目立つ改善点としては, (型推論のための) カリー化が不要になります. っょぃ (小並感).
def foo[A, A](a: A, f: A => A): A = f(a) // Scala2ではこのように型情報を明示するか foo(1, (x: Int) => x * 2) // カリー化する必要があった def bar[A, A](a: A)(f: A => A): A = f(a) bar(1)(x => x * 2) // 一方Dottyはどちらも不要, シンプル! foo(1, x => x * 2)
Scala2では前の引数に依存する型情報 (f
のための A
) を利用するためには型情報を明示するかカリー化する必要がありましたが, Dottyではこのような場合でもカリー化することなく型推論が可能になります.
この例以外にも細かい改善が含まれます.
Literal-based singleton types
値が型のような振る舞いをできるようになります.
object Literals { val fortyTwo: 42 = 42 val `2`: 2 = 2 val fortyFour: 44 = fortyTwo + `2` val text: "text" = "text" def id[T](a: T) = a val two: 2 = id(`2`) }
値を型パラメータに渡してやったり.
forAll { x: Ranged[Int, 1, 100] => val n: Int = x.value // guaranteed to be 1 to 100 }
Trait parameters
trait
にパラメータが渡せるようになります. 内部的にはJava8で拡張された interface
と同じ扱いになるようです.
trait A(x: String) { println(x) } class B extends A("Hello") new B //=> Hello
代わりに以下のような事前定義は廃止されます.
// Dottyではコンパイルエラー class B extends { val x: String = "Hello" } with A new B //=> Hello
@static methods and fields in Scala objects
Scala2ではあくまでシングルトンしか扱えませんでしたが, Javaの static
に相当する静的な定義ができるようになります.
object Foo { @static val x = 5 @static def bar(y: Int): Int = x + y }
Improved Lazy Vals Initialization
lazy
フィールドの初期化メカニズムを変更することで, 初期化の際の潜在的なデッドロックを防げるようになります.
以下の例だと, A
(A.a0
) と B
(B.b
) をそれぞれ別スレッドから初期化しようとするとデッドロックが起きる可能性がありますが, Dottyではこれが防がれます. 他にも特定パターンで発生するStackOverflowも防ぐようです.
object A { lazy val a0 = B.b lazy val a1 = 17 } object B { lazy val b = A.a1 }
Option-less pattern matching (name-based pattern matcher)
以下の2つのメソッドを実装するだけでextractor (unapply
) としてパターンマッチが可能になります. T
がプリミティブ型であれば, Option
に包む際のboxingがなくなるためパフォーマンス面の寄与もありそうです.
def isEmpty: Boolean def get: T
final class OptInt(val x: Int) extends AnyVal { def get: Int = x def isEmpty = x == Int.MinValue // or whatever is appropriate } // This boxes TWICE: Int => Integer => Some(Integer) def unapply(x: Int): Option[Int] // This boxes NONCE def unapply(x: Int): OptInt
Repeated By Name Parameters
=> T*
が Function0[Seq[T]]
を意味するようになります. Scala2ではなぜか可変長引数を名前渡しできませんでしたが, それができるようになります.
def foo(xs: => Int*): Option[Int] = xs.headOption // Scala2ではコンパイルエラー
Multiversal equality
==
や !=
が型安全になります.
Scala2では以下の様なことが起こりえます.
scala> 1 == "1" <console>:8: warning: comparing values of types Int and String using `==' will always yield false 1 == "1" res1: Boolean = false
Int
と String
を比較しているので常に false
になりますが, そもそもこのような比較は意図しない状況で発生していると思います. 上の例ではうまいこと警告が出たものの, 以下のように出ないことも多々あります.
scala> "1" == 1 res2: Boolean = false
ここに Eq
型クラスを導入することで意図しない比較をコンパイル時に弾けるようになります. Eq
が実装されていない場合は eqAny
にフォールバックして比較が行われるようです.
Non-boxed arrays of value classes
Value classの配列がboxing/unboxingしなくなります.
ツールの性能向上
コンパイラの性能向上
- DOT計算ベースに刷新
- Phase fusionによる高速化
- インクリメンタルコンパイルが賢く
- エラーメッセージが賢く
- TASTY
新しい中間ファイル. これを元にJVM/JS/Native向けのコードが吐かれる. マルチプラットフォーム化が容易になったと思われる - Dotty Linkerによる最適化
- Dead Code Elimination
- Automatic specialization
@specialize
による手動最適化が不要に - Convert classes to value classes
条件を満たしていれば自動的にValue classが利用される - Eliminate virtual dispatch
インライン化して呼び出しの最適化 - 諸所の最適化でproguardを使った場合よりもバイトコードのサイズがコンパクトになる
コンパイラ以外の便利ツール
- REPLにシンタックスハイライトが導入
- DottyDoc
- Language Server Protocol 対応
このプロトコルをサポートしている開発環境であればデバッガ等の恩恵を受けられるように
その他の変更点
Macro to scala.meta
従来のマクロは廃止され, scala.meta
に統一されます.
Procedure syntax
返り値の Unit
が必須になります.
def foo() { ??? }
↓
def foo(): Unit = { ??? }
Function with very many parameters
引数22個制限がなくなります.
存在型 (forSome
)
forSome
ではなくワイルドカードで記述するように変更されます.
val x: Array[T] forSome { type T <: Foo } = ???
↓
val x: Array[_ <: Foo] = ???
まだ上記全ての機能が実装されたわけではありません. また, 上記以外にも検討中の機能があります (Java-like Enum, shapeless系, Non-nullable type, etc.).
Dottyの使い方
まだプレビュー版ですがsbtプラグインとして提供されています. Java8が必須です. README通りにやればちょいちょいです.
積極的に人柱になってバグ報告をすると喜ばれると思います.
Scala2からの移行
Scalafixというツールが提供されています. こっちは試してないですが「2to3…ウッ頭が…」ってならないことを信じてます.
まとめ
いいこと尽くめなので早くDottyメインの世界になって欲しいところです. が, まだ実装が終わっていなかったり, 互換性が切れるので既存ライブラリがそれから対応しなければならないことも考慮すると普及するのは少し先になりそうですね (暫くは2系がメジャーで, 3系 (Dotty) がいつからメインストリームになるかは明言されていないようです).
参考文献
- Exploring the future of Scala by Dmitry Petrashko
- The state of Dotty by Guillaume Martres
- Dotty and the new Scala developer experience by Felix Mulder
- Dotty Linker: Making your Scala application smaller and faster by Dmitry Petrashko