以下の続き
- https://xuwei-k.hatenablog.com/entry/2022/02/11/160802
- https://xuwei-k.hatenablog.com/entry/2022/09/03/110341
- https://xuwei-k.hatenablog.com/entry/2024/03/17/112342
- https://xuwei-k.hatenablog.com/entry/2025/04/29/094201
- https://xuwei-k.hatenablog.com/entry/2025/06/19/202643
Scalaアドベントカレンダー2025の記事です
https://qiita.com/advent-calendar/2025/scala
前回書いた時から増えたものについて解説。
既存のruleの改善、改良もしてるがその説明は省略。
https://github.com/xuwei-k/scalafix-rules/compare/v0.6.11...v0.6.19
以前と同様、特に言及がなければSyntacticRuleです。
CaseClassExplicitCopy
case classにcopyという名前での明示的なメソッド定義を禁止する。
case classはそもそもデフォルトでcopyメソッドが自動生成されるが、明示的に別のcopyを定義した場合に
といった挙動が、Scala 2と3で微妙に異なり、2から3へのupdate時に問題になる可能性がある。
万が一Scala 2と3で挙動が同じだったとしても明らかに分かりづらいので、いずれにせよ禁止の方がいいと思われるので。
ConstructorPrivateVal
caseがついていない普通のclassにおいて、以下の2つはほとんど違いがない
class A(x: Int)
class A(private val x: Int)
正確には
private明示した方は、もちろんprivate- 何も書いてない方は
private[this]相当(こちらの方がよりスコープが狭い)
となるので、微妙に違いがある。とはいえ、その違いが必要な場合はあまり多くない。
一応、implicitなvalue classを定義する場合は明示的に private val にしたいことはある。
よって、可能な範囲で「private valが必要なさそうだなぁ」と判断したら、それを消す。
消したほうが、記述も短く、スコープも厳密にはさらに小さくなって、大抵望ましいので。
DefImplicitParamToUsingParam
Scala 2切り捨ててScala 3だけにした場合にマイグレーションするためのrule。
defのimplicit paramをusingに変更する。
つまり
- def foo(a: Int)(implicit ec: ExecutionContext): Future[Int] + def foo(a: Int)(using ec: ExecutionContext): Future[Int]
という書き換え。
全てのimplicit撲滅したい場合に、順番的に最初の方にやるもの
EitherToTry
EitherにはtoTryというメソッドがある。それなのにわざわざmatch式で変換してる場合にtoTryに置き換えるだけのもの
EnumUnnecessaryExtends
enum A {
case B extends A
case C extends A
}
これは省略して以下のように書いても同等になる
enum A {
case B
case C
}
「省略可能かどうか?」を判断して、それを書き換えてくれるもの。
「省略可能かどうか?」の詳細な条件は結構ややこしくて、完璧に判断するのは面倒なので、全てのパターンで完璧に消してくれるわけではなく、明らかにバグらずに消せる単純なパターンしか消さない。
FilterHeadOption
これはSemanticRule。
wartremoverにも同様のものがある。
Seqなどを
seq.filter(f).headOption
していたら、1つしか必要ないのに全部をfilterするのは基本的に無駄である。
(filterに渡した関数に、明示的に副作用がある場合を除く)
よってそれを
seq.find(f)
に書き換える。
ForeachEntry
これもSemanticRule。
これもwartremoverに同様のものがある。
Scala 2.13からMapにforeachEntryというメソッドがあり、それが使える場合に書き換えるもの。
ここからはforeachEntry自体の説明になるが、
Mapに対してforeach呼び出した場合は
Function1[(K, V), A]
を渡すことになる。
しかし、Mapというのは内部でkeyとvalueをTuple2で保持しているとは限らないので、foreachのためだけに一時的なTuple2が生成されることになる可能性がある。
foreachEntryはforeachとは微妙に異なり
Function2[K, V, A]
を受け取ることによって、余計なTuple2生成を避けたり、引数部分で
map.foreach { case (k, v) =>
map.foreachEntry { (k, v) =>
と、(それ以上のパターンマッチをしない場合) caseを省略出来るメリットがある。
SelfTypeNamePlaceholder
Scala 2では
trait A { _ : B =>
は許可されるが、Scala 3ではエラーになる。
よってScala 3準備のためにScala 2の状態でこれを検知して禁止するためのもの。
SortedMaxMin
これはSemanticRule。
以前ほぼ同じものをSyntacticRuleで書いたことがあったが、それを忘れてSemanticRuleでも書いてしまった。
SyntacticRuleとSemanticRuleそれぞれメリット、デメリットがあるので*1、両方存在するのは問題ないが、名前に整合性がなくて微妙なので、後で名前を変えるかもしれない。
TryToEither
EitherToTryの逆。Tryにも toEither メソッドがあるが、それを知らずに?match式で変換していた場合に toEither に書き換える。
TypeProjection
Scala 3では一部のtype projectionが禁止になるので、そのパターンScala 2で検知したい場合あるよなぁ、やってみるか〜???
https://docs.scala-lang.org/scala3/reference/dropped-features/type-projection.html
と思って作ってみたが
「Scala 3で禁止されるtype projectionかどうか?」
の正確な判断は思ったより難しくて、作ってみたはいいが、中途半端過ぎるので、使い道が微妙になってしまったrule。
WildcardType
Scala 3ではワイルドカードが F[_] から F[?] になる。
その書き換えをするだけのもの。
これはscalafmtで強制可能なので、scalafmtを使っているなら、このscalafix ruleに頼らず、基本的にそれを使った方が良い。
これは
- scalafmtを導入してないproject向け
- scalafmt導入してるのだけどversionが低くて強制書き換えがまだ使えない
- scalafmt導入済みだが、段階的に細かく書き換える都合上、他のものは一切書き換えずに、ワイルドカードだけを狙って書き換えたい
といった特定のパターンのみに使うことを想定している