Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アルプでのScala 3移⾏

アルプでのScala 3移⾏

kenji yoshida

March 04, 2022
Tweet

More Decks by kenji yoshida

Other Decks in Programming

Transcript

  1. 近況 相変わらず某社でScala 書く仕事してます もう3 年経った 家に引きこもっている 最近アルプで副業はじめた 2022 年2 月くらいから

    OSS 活動も地味に続けてるが、話すと時間なくな るので省略 例えばscalafix 頑張ったり blog1 blog2 3 / 66
  2. Scala version アップデート に向けてやること これは一般論。Scala 3 に限らない 1: 依存ライブラリ 調査と整理とアップデート

    2: コンパイル通す 3: テスト通す 通常1 => 2 => 3 という順番だが 一部並列して実行可能 12 / 66
  3. Scala 3 の超基本 (2.13 と比較して) ソース互換はそれなりにある Scala 2 でオプション指定すれば3 で非推奨になるものが事

    前にある程度わかる 関連scalafix 作ったりした バイナリ互換は微妙にある( 詳細は複雑) Scala 3 は2.13 のlibrary.jar に依存 macro, reflection は全く互換ない macro annotation やcompiler plugin も 14 / 66
  4. 依存ライブラリ調査 Scala 3 向けでリリースされてないものは何か? その中でmacro やreflection に依存しているも のは? メンテされてないものはやめることも検討? リリースされてなくても

    for3Use2_13 でコン パイルは通せるか? その方法で、とりあえず通せるものと、通せないものがある macro 使ってたり、その他特殊なケースはだめ 15 / 66
  5. sbt のconflictWarning // `_3` と `_2.13` のものが混ざると依存解決時点でエラーになるが // Scala 3

    の場合は一旦は警告のみにし、エラーにはしないようにする。 // TODO `_3` と `_2.13` のものが混ざった場合 // バイナリ互換が保証されず // 最悪実行時にエラーになる可能性があるので、 // 本格的にScala 3 移行するときまでには、 // この抑制の設定がなくてもビルド可能になるように修正する conflictWarning := { if (scalaBinaryVersion.value == "3") { // ウザかったらwarn より下げてもいいぞ! ConflictWarning("warn", Level.Warn, false) } else { conflictWarning.value } } 20 / 66
  6. libraryDependencySchemes Scala 2 でも発生するので3 に限った話ではない scala-xml やscala-parser-combinators あ たりがよく引っかかる? バイナリ互換が無いものが混ざってるかもしれな

    いが、とりあえず雑に最新を採用してしまう設定 にしたいなら以下のように設定 libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % "always" 最終的には本当にバイナリ互換あるのか?しっか り確認した方がいいぞ! 23 / 66
  7. scala> val f = { x: Int => x }

    -- Error: ------------------------------------------------ 1 |val f = { x: Int => x } | ^ |parentheses are required around the parameter of a lamb |This construct can be rewritten automatically under -re 25 / 66
  8. -Xsource:3 Scala 2.12 や2.13 の最新で設定可能 compiler がScala 3 の挙動に少し近くなる Scala

    3 の一部のsyntax がScala 2 でも使用可 能になる 恒久的に設定しなくても、少し試してみると移行 準備にはなるぞ 27 / 66
  9. ビルドファイルでversion による分岐 変数束縛する場合はさらにこれを Def.setting で包 むなど val deps = Def.setting(

    scalaBinaryVersion.value などのKey 使った定義 ) // 他のところ // `.value` つけて呼び出し libraryDependencies ++= deps.value 29 / 66
  10. libraryDependencies ++= { if (scalaBinaryVersion.value == "3") { Nil }

    else { Seq(compilerPlugin( "org.typelevel" % "kind-projector" % "0.13.2" cross )) } } 30 / 66
  11. Scala 3 の場合だけskip するのをtest class 単 位で設定 Test / testOptions

    ++= { if (scalaBinaryVersion.value == "3") { // 変更頻度が高い場合、 // これもconfig から読むようにすると良い? val excludeTestNames = loadScala3Config("exclude_tests Seq( Tests.Exclude(excludeTestNames), ) } else { Nil } } https://github.com/sbt/sbt/blob/v1.6.2/mai actions/src/main/scala/sbt/Tests.scala#L89 L98 31 / 66
  12. sbt のproject 毎に徐々に 対応するためのテクニック 例: sbt のsub project が複数あって a1,

    a2: test まで全部通る a3: Test/compile は通るがtest 通らない a4: main 側のcompile だけ通る a5: main 側のcompile すら通らない この時sbt に渡すべき引数とは? 32 / 66
  13. Scala 3 対応の現状 Spark 除いてtest コード含めてcompile は通り そうな状態 これからtest 通していく予定

    実際に動かすには、結局ライブラリ待ちも多少あ る 出したpull req 60 個以上 38 / 66
  14. 依存ライブラリ系 全く使っていない依存ライブラリや設定削除 #8541 +0 -8 circe の依存整理 #8544 +1 -12

    kind-projector の依存をCrossVersion.full にして最新に #8545 +7 -8 semanticdb の依存の書き方を変更 #8560 +9 -1 明示的な scala-java8-compat の依存の記述削除 #8599 +3 -4 意味がないcats の明示的依存削除 #8597 +0 -1 scala-parallel-collections を最新に更新 #8613 +1 -1 某library が色々厳しいので改変しつつ必要な部分だけ組み込み #8614 +290 -10 scala-parallel-collections の依存の書き方修正(Scala 3 準備) #8885 +2 -2 circe-generic-extras の依存定義を必要なところのみに移動 #9018 +9 -5 Scala 3 準備のために org.jetbrains annotations の依存追加 #8916 +10 -0 https://github.com/lampepfl/dotty/issues/13523 40 / 66
  15. ビルド設定 wartremover 追加 #8641 +12 -0 wartremover 自体はむしろScala 3 未対応だが、Scala

    3 で非推奨な機能の警 告出すのに使う build ファイル内でScala 3 の準備のための設定追加 #8790 +14 -1 implicit に型が書いてなかったら警告するscalafix rule 追加 #8948 +1 -0 ExplicitImplicitTypes というscalafix rule 41 / 66
  16. コード修正 非推奨なscala.App をやめて明示的なmain 定義 #8665 +401, −234 公式ドキュメント 中途半端な?scala.App 書き換えscalafix

    procedure syntax 修正 #8668 +6 −5 wartremover で非推奨なscala.App を使用禁止に #8670 +4 -1 Scala 3 で動かないのでcirce のJsonCodec 全て削除 #8700 +1041 −395 CirceCodec 書き換えscalafix circe のJsonCodec マクロアノテーションの実装 呼び出し側と定義側で括弧の有無揃える #8768 +26 −27 Scala 3 で消えるdo-while を書き換え #8779 +3 -2 42 / 66
  17. コード修正 Scala 3 に備えてkind-projector でのinfix やめる #8783 +6 -6 それ用のscalafix

    Scala 3 本体の議論 コンパイルエラーになる型パラメーターと同じtype member 削除 #8784 +0 -2 Scala 3 の準備のためにshepeless 使った3 で動かない部分を分割 #8791 +135 −109 shapeless 2 に依存した未使用のclass 削除 #8792 +0 -62 Scala 2.13 での警告修正( 呼び出しと定義の括弧の付与揃える) #8796 +5 -5 override してsub type を返している場合に型を明示(Scala 3 準備) #8857 +5 -5 export がScala 3 の予約語なのでバッククオートで囲う #8866 +6 -6 重複している必要ないimplicit 削除 #8871 +7 -7 scalatest のMatcher のimplicit def が名前指定でimport されてるのを修正 #8874 +2 -2 shapeless のHList の書き方修正(Scala 3 準備) #8880 +75 −72 パターンマッチ部分に型を書くと逆にエラーになる場合があったので型を消す #8886 +4 -5 43 / 66
  18. コード修正 Function1 の引数に必ず括弧を付与 #8887 +49 −48 https://github.com/xuwei-k/scalafix- rules/commit/384782c38bc688eaa1acec57d30b2c2db3881391 Scala 3

    で自動で導出されないのでUnit に対するテスト用のインスタンス明示的に追 加 #8890 +4 -0 cats.implicits をcats.syntax.all に置き換え #8908 +185 −213 https://github.com/typelevel/cats/issues/4138 Scala 3 bug 回避のためcompanion object のcase を消す #8910 https://github.com/lampepfl/dotty/issues/12919 case class ではないclass のEncoder でmacro 使うのやめる(Scala 3 準備) #8911 +13 −3 case class でないものに対するderving ができないので導出された型クラスインス タンスの使用やめる #8914 +4 -4 重複しているimplicit 削除 #8918 +1 -1 shapeless 2 依存でそのままでは動かないものを削除、書き換え #8953 +7 -51 使ってないのにcirce-generic-extras のConfig を生成している箇所を削除 #8961 +1 -5 44 / 66
  19. scalikejdbc アップデート scalikejdbc のbatchByName でscala.Symbol 使っている箇所修正 #8723 +29 -29 scalikejdbc

    をupdate する準備として括弧削除 #8907 +113 −109 scalikejdbc のapply に括弧を付与(scalafix で) #8952 +247 −226 scalikejdbc を3.5.0 から4 にアップデート #8999 +25 −13 45 / 66
  20. ひたすらimplicit に型を付与 implicit に型書きつつvalue class に #8760 +12 −8 #8777

    +167 −107 #8653 +37 -35 #8865 +20 -21 #8876 +43 -29 #8895 +44 -32 #8923 +41 -20 #8931 +49 -46 47 / 66
  21. すぐに必須では無い? が警告修正系 object に対するfinal 削除 #8780 +2 -2 Unreachable Warning

    修正 #8888 +0 -3 テスト内部の重複している type R 削除 #8915 +0 -1 #8964 +0 -1 null 以外でmatch する可能性がないパターンマッチのcase を消す #8955 +13 −67 48 / 66
  22. sub type が返らない件 Welcome to Scala 3.1.1 (1.8.0_322, Java OpenJDK

    64-Bit Ser Type in expressions for evaluation. Or try :help. scala> trait A | | class B extends A | | trait X { | def foo: A | } | | class Y extends X { | override def foo = new B // ここの型を省略するかどうか | } | | val y = new Y | | y.foo // Scala 3 ではB ではなくA 型でかえる。Scala 2 ではB 49 / 66
  23. implicit を2 回書くの禁止 Welcome to Scala 3.1.1 (1.8.0_322, Java OpenJDK

    64-Bit Ser Type in expressions for evaluation. Or try :help. scala> class A(a: Int)( | implicit b: String, | implicit val c: Boolean) -- [E015] Syntax Error: ---------------------------------- 3 | implicit val c: Boolean) | ^^^ | Repeated modifier implicit 50 / 66
  24. HList の件でpull req に書いた説明 HList のapply は 「implicit parameter でScala

    2 macro で生成されたもの(Generic) 」 を受け取っ ているので、このままではScala 3 でコンパイルが通らないため。 https://github.com/milessabin/shapeless/blob/v2.3.8/core/src/main/scala/shapeless/hlists.scal https://github.com/milessabin/shapeless/blob/v2.3.8/core/src/main/scala/shapeless/generic.sca このapply というのは、任意のcase class やTuple を受け取って、HList に変換するためのメソッドである。 可変長引数のように見えるが、これはTuple を渡している。 implicit parameter でScala 2 macro で生成 されたものに関しては、無理やり頑張ってScala 3 のmacro で生成するのは不可能ではないが、Scala 2 の implicit が勝手にスコープに入るため、もしapply 使い続けるならば、「Scala 2 と3 でソースコード分けつ つ、implicit を明示的に渡す」といった変なことをやらないといけないので、HList をそのまま書く方式に変 えた。 これは結局HList のapply で返ってくる結果そのものなので、compile 時の処理的にも実行時の処理的 にも、この方が処理することが減るはずである。 ある程度は以下のscalafix で自動修正したが、format が崩 れたりコメントが消えてしまったところやimport を手動で修正した。 51 / 66
  25. shapeless 2 のHList をScala 3 で使う Before (Scala 2 のmacro

    依存) HList(a, b, c) After ( とりあえずScala 3 で動く) a :: b :: c :: HNil 52 / 66
  26. import scalafix.Patch import scalafix.v1.SyntacticDocument import scalafix.v1.SyntacticRule import scala.meta.Term class ShapelessHListApply

    extends SyntacticRule("Shapeless override def fix(implicit doc: SyntacticDocument): Patch doc.tree.collect { case t @ Term.Apply(Term.Name("HList"), args) => Patch.replaceTree( t, args.mkString("(", " :: ", " :: HNil)") ) }.asPatch } } 53 / 66
  27. 生成されるコード class X { def a(b: Boolean): Int = ???

    def a$default$1: Boolean = true } val x = new X x.a(x.a$default$1) 58 / 66
  28. 内部実装依存な力技。 これらのデフォルト引数扱 うだけなら、macro もreflection も必要なかった if ( realMethod.isInvokable && (

    methodName.contains("$default$") || ExecuteIfSpecialised(methodName) ) ) i.callRealMethod() https://github.com/mockito/mockito- scala/blob/3f7dbfaac58/common/src/main/scala L27 61 / 66
  29. scalikejdbc のapply 括弧の自動付与scalafix import scalafix.Patch import scalafix.v1.SyntacticDocument import scalafix.v1.SyntacticRule import

    scala.meta.Term class ScalikejdbcApplyParentheses extends SyntacticRule("S override def fix(implicit doc: SyntacticDocument): Patch doc.tree.collect { case a @ Term.Select( Term.Select(_, Term.Name("list" | "single" | "upda apply @ Term.Name("apply") ) if a.parent.forall(!_.is[Term.Apply]) => Patch.addRight(apply, "()") }.asPatch } } 63 / 66
  30. type param とtype member 同じだと怒られる Welcome to Scala 3.1.1 (1.8.0_302,

    Java OpenJDK 64-Bit Ser Type in expressions for evaluation. Or try :help. scala> trait A[B] { type B } -- [E161] Naming Error: ---------------------------------- 1 |trait A[B] { type B } | ^^^^^^ | B is already defined as type B 1 error found 64 / 66