Scala 3 最新情報
~リリースに向けて準備しよう~
自己紹介
- Taisuke Oe
- Twitter: @OE_uia
- GitHub: @taisukeoe
- フリーランスエンジニア
- ScalaMatsuri主催してます
Scala 3.0.0-M1のリリースが近い!
(Thanks to Dotty Team & Scala Center!)
- これまでdottyというコードネームで開発が進んでいたもの
- Scala 3になるというアナウンスは以前からされていた
Scala 3のmigrataion guideが
整ってきました
(Thanks to Scala Center!)
ref: Scala 3 Migration guide · An evolving guide to support the migration to Scala 3
今回の勉強会では
- Scala 3 - 2.13.x 互換性最新版
- ライブラリごとの Scala 3 対応状況チェックポイント
- マルチプロジェクトでの Scala 3 お試し方法
についてお送りします
Scala 3の新機能や、非互換性についての詳細は取り扱いません
Scala 3 - Scala 2.13
互換性最新情報
互換性の軸
- ソースの互換性
- コンパイル時のバイナリの互換性
- マクロの互換性
ソースの互換性
- Scala 2.13.2以上で、`-Xsource:3` scalac optionを利用可能
- Scala 3非互換のコードを、ある程度エラーにしてくれる
- ref: Scala 2.13で-Xsource:3を指定すると変更される挙動一覧 - xuwei-k's blog
- 全ての非互換コードがエラーになるわけではない
- Scala 3 コンパイラ(dotc)の `-source:3.0-migration` オプションで、エラーになるコードの一部は警告に格下げされる
- ref: Scala 3 Migration Mode · Scala 3 Migration guide
- `-rewrite` オプションを追加すると、一部は自動で書き換わる
- ref: Incompatibility Table · Scala 3 Migration guide
- Scalafix rulesも一部あるが、まだまだこれから
ソースの互換性
コンパイル時のバイナリの互換
両方向について扱える
- Scala 2.13系から、Scala 3.0のビルド成果物を使える(前方互換性)
- Scala 3.0から、Scala 2.13系のビルド成果物を使える(後方互換性)
なお、Scala 3でビルドすると.jarの他に.tastyというバイナリを生成する。
Scala 3 バイナリを利用するときはjarではなくTASTyを利用することが、互換性維持戦略の前提になっている。
コンパイル時のバイナリの前方互換
- 前方(Scala 2.13 -> Scala 3.0 TASTy)
- ライブラリの、Scala 3 & 2.13のクロスビルドが理論上は不要になる
- ライブラリのScala 2.13サポートが切られても、Scala 2.13からScala 3 artifactを利用できる
- Scala 2.13.4以上で対応(TASTy readerが組み込まれる)
コンパイル時のバイナリの後方互換
- 後方 (Scala 2.13 jar <- Scala 3)
- 依存ライブラリがScala 3.0ビルドされていなくても、自プロジェクトをScala 3にアップデートできる
コンパイル時のバイナリの互換
- 前方(Scala 2.13 -> Scala 3.0 TASTy)
- 後方 (Scala 2.13 jar <- Scala 3)
両方向の互換があることで、Scala 3とScala 2.13を混ぜてことができる。
段階的なmigrationが可能になる。
マクロの互換性
- Scala 3と、Scala 2.13のマクロAPIは全く違う
- マクロの定義に互換性はない
- Scala 2.13マクロは、Scala 3版に自力で書き換えなければいけない
- Scala 3ではマクロを使わなくても実現できる場合がある
- Scala 2.13マクロで出来ていた一部は、Scala 3マクロではできない
- TASTy Reflectionを使えばある程度安全ではないことができる…?
- ref: Migrating Macros · Scala 3 Migration guide
- 自動書き換えも厳しい
- Scala 2.13マクロは、Scala 3版に自力で書き換えなければいけない
- マクロの定義に互換性はない
マクロの互換性
- Scala 3と、Scala 2.13のマクロAPIは全く違う
- マクロの展開も互換性はない
- Scala 2.13マクロ実装(macro impl)を、Scala 3から直接呼び出して展開することはできない。逆も然り。
- 『「Scala 2.13のマクロ実装」を呼び出して展開しているScala 2.13のメソッド』をScala 3から呼び出すことはできる。
- マクロの展開も互換性はない
- Scala 3.0には、Scala 3のマクロと、Scala 2.13のマクロの両方を書くことができる
- Scala 2.13から参照すると、Scala 2.13マクロ実装 in Scala 3が使われて展開される
- ref: Add experimental Scala 2 macro compat by nicolasstucki · Pull Request #8811 · lampepfl/dotty
- つまりマクロのためにScala 2.13 / Scala 3クロスビルドする必要はないはず
マクロの互換性
Scala 3 Migration Guideのロードマップ
- M1 - Dotty書き換えルールのドキュメント化
- M2 - コミュニティナレッジの移管
- M3 - 互換性リファレンスの公開 <- イマココ
- M4 - ScalaCenterパートナー企業によるベータテスト
- M5 - メタプログラミング・マイグレーションガイドの準備開始
- M6 - ライブラリ・チュートリアルの公開とテスト
- M7 - アプリケーション・チュートリアルの公開とテスト
- M8 - モチベーション・ページの公開
- M9 - マイグレーションガイドをScala Webサイトに統合
ref: Scala 3 Migration Guide (continuation) - Scala 3 Release Projects - Scala Contributors
ライブラリの
Scala 3対応状況
一口に「対応」といっても様々
「対応」=「Scala 3から問題なく使える」と定義すると
- Scala 3ビルドのartifactが提供されている
- Scala 2.13のartifactが、Scala 3から利用可能
- Scala 2.13のartifactが、Scala 3から何らかの理由で利用できない
Scala 3ビルドのartifactが提供されている
- まだめちゃくちゃ少ない
- 少なくともdottyでビルドできているプロジェクト
Scala 2.13ビルドのartifactが利用可能
- macroやコード生成を含まないライブラリだと理論上は使える
- sbt-dottyで withDottyCompat すると、Scala 3からScala 使える
libraryDependencies += ("a" %% "b" % "c").withDottyCompat(scalaVersion.value)
Scala 2.13ビルドのartifactが利用できない
以下のようなケース
- コード生成される&生成コードがScala 3でコンパイルできない
- Scala 2.13 macroを含んでいる
- CompilerPlugin(Scala 2コンパイラ内部に依存している)
できることとしては
- contributeする
- 対応を待つ
- macro実装の直接の呼び出しはScala 2.13サブプロジェクトでやる
マルチプロジェクトビルド
でどうやってScala 3を取り入れる?
こんなプロジェクトがあったとする
domain
adapter
utils
app
こんなプロジェクトがあったとする
domain
adapter
utils
app
lazy val app = project
.enablePlugins(PlayScala)
.dependsOn(adapter, utils)
lazy val adapter = project
.settings(
libraryDependencies +=
"com.typesafe.play" %% "play-json" % "2.9.1"
).dependsOn(domain)
val scala213 = "2.13.3"
ThisBuild / scalaVersion = scala213
lazy val domain = project
lazy val utils = project.dependsOn(adapter)
Scala 3導入の順序
- Scala 2.13.4に上げる
- sbt 1.4.0に上げる
- 依存ライブラリのScala 3対応について調べる
- 依存グラフの下流からScala 3に上げる
Scala 2.13.4に上げる
- Scala 2.13.4で、TASTy Readerがバックポートされた
- すなわち、Scala 2.13.4からScala 3 artifact (.tasty)を読める
- 2020/9/28現在、Scala 2.13.4はM, RCともにリリースされてない
- 今回は9/24の最新の2.13.4-bin-d66ebf4 SNAPSHOTを使う
sbt 1.4.0に上げる
- sbt 1.4.0から、Scala 2.13 - Scala 3のサブプロジェクト間依存が可能になった (Thanks to sbt team!)
- 2020/9/28 現在 1.4.0-RC2がリリースされている
- 今回は1.4.0-RC2を利用する
2.13.4に上げた
domain
adapter
utils
app
lazy val app = project
.enablePlugins(PlayScala)
.dependsOn(adapter, utils)
lazy val adapter = project
.settings(
libraryDependencies +=
"com.typesafe.play" %% "play-json" % "2.9.1"
).dependsOn(domain)
// 未リリースのためSNAPSHOT
val scala213 = "2.13.4-bin-d66ebf4"
ThisBuild / scalaVersion = scala213
lazy val domain = project
lazy val utils = project.dependsOn(adapter)
ライブラリのScala 3対応状況を調べる
- 情報収集
- github issue
- gitter channel
- Migration Status · Scala 3 Migration guide
- 実際試す
今回のサンプルプロジェクトはPlay利用
- PlayFramework ... Scala 3未対応
- play-json ... Scala 3未対応
- データ型のcodec(e.g. reads, writes)をmacroで生成・導出している(参考: play-json/JsMacroImpl.scala)
domainとutilsだけDotty 0.27.0-RC1へ
domain
adapter
utils
app
lazy val app = project
.enablePlugins(PlayScala)
.dependsOn(adapter, utils)
lazy val adapter = project
.settings(
libraryDependencies +=
"com.typesafe.play" %% "play-json" % "2.9.1"
).dependsOn(domain)
// 未リリースのためSNAPSHOT
val scala213 = "2.13.4-bin-d66ebf4"
val scala3 = "0.27.0-RC1"
ThisBuild / scalaVersion = scala213
lazy val domain = project
.settings(scalaVersion := scala3)
lazy val utils = project
.settings(scalaVersion := scala3)
.dependsOn(adapter)
各サブプロジェクトの概観
domain (Dotty 0.27.0-RC1)
- 外部依存が少ない
- Scala 3にアップデートしやすい
- (今回はmigrationを推奨するものではないが)幾つかのケースでScala 3の機能が便利?
- 代数的データ型(ADT)の定義でenum
- 実態はただのsealed traitとsub typeなので分かりやすい
- opaque type
- opaque type Name = String
- Scala 3系だと別の型だけど、Scala 2系から見るとただのtype aliasとして扱われる(?)ので注意
- 代数的データ型(ADT)の定義でenum
domainのコード (Dotty 0.27.0-RC1)
package domain
object User {
opaque type Id = Int
opaque type Name = String
object Id {
def apply(i: Int): Id = i
}
object Name {
def apply(n: String): Name = n
}
}
case class User(id: User.Id, name: User.Name)
adapter (Scala 2.13.4)
- play-jsonはmacroを使っている&Scala 3対応してない
- Scala 2.13の中では使える
- 現状、Scala 3のサブプロジェクトで定義したデータ型のコンストラクタシグネチャをTASTyで正しく読めない
- そもそもTASTy readerもScala 2.13.4がWIPなので、現状これがバグなのか仕様(というか限界なのか)判別つかない
adapterのコード (Scala 2.13.4)
package adapter
//some imports here...
object UserFormat {
// Failed to read User constructor from Scala 3 TASTy. Why?
// implicit val userFormat: Format[User] = Json.format[User]
// Explicit read codec works fine.
implicit val userReads: Reads[User] = (
(JsPath \ "id").read[Int] and
(JsPath \ "name").read[String]
)(User.apply _)
// Watch out: User.unapply signature has been changed.
implicit val userWrites: Writes[User] = (
(JsPath \ "id").write[Int] and
(JsPath \ "name").write[String]
)(u => (u.id, u.name))
}
adapterのコード (Scala 2.13.4)
package adapter
import domain._
import play.api.libs.json._
case class Post(user: User, content: String)
object Post {
import adapter.UserFormat._
implicit val postFormat: Format[Post] = Json.format[Post]
}
utils(Dotty 0.27.0-RC1)
- Json変換をplay-jsonに委譲するだけのモジュール
- Scala 3から、play-jsonを間接的になら利用できる
utilsのコード(Dotty 0.27.0-RC1)
package utils
import domain._
import adapter._
import User._
import UserFormat._
import play.api.libs.json._
object Converter {
// You can use play-json codec here, since this doesn't call macro expansion directly.
def toJson(post: Post): JsValue = Json.toJson(post)
def toJson(user: User): JsValue = Json.toJson(user)
}
app (Scala 2.13.4)
- PlayFrameworkのcontrollersとviews
- おおよそplay-scala-seed.g8のまま
- PlayFrameworkはScala 3に対応していないが、Scala 3で実装したサブプロジェクトに依存可能
appのコード(Scala 2.13.4)
package controllers
//some imports here...
@Singleton
class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def index = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index())
}
def hello(id: User.Id, name: User.Name) = Action { implicit request: Request[AnyContent] =>
Ok(Converter.toJson(Post(User(id, name), "Hello, world!")))
}
}
今回のサンプルコード置き場
まとめ
本日のまとめ
- Scala 3.0.0-M1が間もなくリリース予定。
- 今すぐできること
- Scala 2.13.2以上に上げて、 `-Xsource:3` でコンパイル通す
- もうすぐできること
- Scala 2.13.4とsbt 1.4.0が出たら、マルチプロジェクトビルドの一部をScala 3.0に上げてみる。
- dotcの`-source:3.0-migration`
- `-rewrite` オプションやScalafixも使えそうなら使ってみる
- migrationというよりも、Scala 3 お試し&フィードバックのため。
- Scala 2.13.4とsbt 1.4.0が出たら、マルチプロジェクトビルドの一部をScala 3.0に上げてみる。
Scala3最新情報
By Taisuke Oe
Scala3最新情報
- 933