(※2018/3/17 新しい記事書きました) Dottyによる変更点と使い方
新しい記事を書きました. 本記事を包含します.
Dottyで何ができるようになるのかとその使い方を簡単にまとめたいと思います. 理論的な部分は深掘りしません.
以下ちょっと長め. 調べきれてない点も多々あるので, 抜けや間違いがあったら教えてください.
そもそもDottyとは
Dependent Object Types (DOT) に基づいた新しいScalaコンパイラです. たまにScala3とか呼ばれたりもしています. DOT計算理論の詳細は省きます (まとめるほど理解できていないとも言う…). 現行のコンパイラと比べ, コンパイラサイズの減少・コンパイル速度向上・様々な機能追加の他, コンパイラ自体の開発安定性も増すパワフルなアップグレードが期待できます. 現行のScalaとの互換性は切れていますが, 勿論重要な機能がなくなるといったことはなく移行ツールも用意されています (詳細後述). また, Scala2.11系でビルドされたライブラリであればそのまま使えます (2.12系は現時点では無理なようです).
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 // Dottyの交差型 (A & B) # T //=> Int & Double // 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, f: A => A): A = f(a) // Scala2ではこのように型情報を明示するか foo(1, (x: Int) => x * 2) // カリー化する必要があった def bar[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も防ぐようです. スレッドセーフにする場合は @volatile
が必須になる.
object A { @volatile lazy val a0 = B.b lazy val a1 = 17 } object B { @volatile lazy val b = A.a1 }
Option-less pattern matching (name-based pattern matcher) Dottyの新機能扱い ですが, もうScala2で使えます
以下の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しなくなります.
Function arity adaption
引数マッピングの効率が良くなります
val pairs = Seq((1, 2), (3, 4)) // Scala2ではこうする必要があったが pairs.map { case (x, y) => x + y } // Dottyでは簡潔に書けるようになります pairs.map((x, y) => x + y)
Named type parameters
型引数に名前を付けることができるようになり, 併せて名前付き型引数の部分適用が可能になります
trait Map[type Key, type Value] type IntMap = Map[Key = Int]
Contravariant implicit
反変implicitが利用可能になります
trait A[-T] case class B[T]() extends A[T] class X class Y extends X implicit val x = B[X] implicit val y = B[Y] implicitly[A[X]] implicitly[A[Y]] // Scala2だとこれはコンパイルエラー
あまり例に自信がないけど多分こんな感じだと思います
Implicit function type
簡単に言うと, コンテキスト等をimplicit parameterで引き回す際に生まれるボイラープレートを省略できるようになります.
type Viewed[T] = implicit Viewer => T def f(x: Int): Viewed[Double] = ??? // こう書くだけで // 最適化を経てカリー化されたimplicit parameterを使った形に展開される // def f(x: Int)(implicit v: Viewer): Double = ???
ツールの性能向上
コンパイラの性能向上
- DOT計算ベースに刷新
- Encoding type parameters
高階型を表現するために型引数情報がメンバとして扱われるようになる. Named type parametersはこれをうまいこと利用してるみたい
trait List[T]
→trait List { self => type T }
- Encoding type parameters
- Phase fusionによる高速化
- インクリメンタルコンパイルが賢く
- エラーメッセージが賢く
- TASTY
新しい中間ファイル. これを元にJVM/JS/Native向けのコードが吐かれる. マルチプラットフォーム化が容易になると思われる - Dotty Linkerによる最適化
コンパイラ以外の便利ツール
- REPLにシンタックスハイライトが導入
- DottyDoc
- Language Server Protocol 対応
このプロトコルをサポートしている開発環境であればデバッガ等の恩恵を受けられるように
その他の変更点
Macros 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.). また, 一部機能はScala2にも実装が行われてい(ます|く予定です).
Dottyの使い方
まだプレビュー版ですがsbtプラグインとして提供されています. Java8が必須です. README通りにやればちょいちょいです.
Scala2との相互運用
Scala2.11系でビルドされたライブラリを使うことができます. 例えばdependencyをこんな感じにすればok.
libraryDependencies += "org.json4s" % "json4s-native_2.11" % "3.5.1"
積極的に人柱になってバグ報告をすると喜ばれると思います.
Scala2からの移行
Scalafixというツールが提供されています. こっちは試してないですが「2to3…ウッ頭が…」ってならないことを信じてます.
まとめ
いいこと尽くめなので早くDottyメインの世界になって欲しいところです. が, まだ実装が終わっていなかったり, 互換性が切れるので 既存ライブラリがそれから対応しなければならない (今のナシ!Scala2.11系でビルドされたものならすぐに使えます!2.12系でビルドされたものは現時点では使えないようです) ことも考慮すると普及するのは少し先になりそうですね (暫くは2系がメジャーで, 3系 (Dotty) がいつからメインストリームになるかは明言されていないようです).
追記: ふんわりとしたロードマップは発表されていました (page 65 in What to leave implicit by Martin Odersky)
参考文献
- 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
- Implementing higher-kinded types in Dotty by Martin Odersky
- Scala the road ahead by Martin Odersky
- What to leave implicit by Martin Odersky