「可変長引数パターン」のご紹介:Scala 3のパワーアップした表現力(2)

Scala 3.3.4
最終更新:2023年12月7日

[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください

この記事では、Scala 3の「可変長引数パターン」(Vararg Patterns)についてご紹介します。

可変長引数パターンは、パターンマッチで可変長引数を取り出すために使用する

可変長引数パターンとは、match式などのパターンマッチにおいて、リストの要素を可変長引数として取り出す方法のことです。

可変長引数パターンは以下のように記述します。

Scala 3
list match { case List(1, 2, xs: _*) => ??? // xsはリストで取得できる case List(1, 2, _: _*) => ??? // 束縛しない場合 case _ => ??? }

この例では、リストが1と2から始まる場合に、これに続く要素をリストとして取得しています。
この記述方法が関数リテラル等における可変長引数の記法と同じなので「可変長引数パターン」と呼ばれます。

変数に束縛する必要がない場合にはアンダースコア(_)で代替します。

使い方を具体的に見てみましょう。

def go(list: Seq[Int]): Unit = { list match { case List(1, 2, xs: _*) => println(s"3番目以降: $xs") // A case List(1, _ : _*) => println(s"1個だけ") // B case _ => println(s"空") // C } }

ここでは、goメソッドを定義します。
1と2から始まる場合、1から始まる場合、それ以外の場合の3通りの分岐があります。

このgoメソッドに対して以下の3つのリストを渡してみます。

val xs = List(1, 2, 3, 4) val xs2 = List(1, 2) val xs3 = List(1)
go(xs) go(xs2) go(xs3)

結果は以下のようになります。

3番目以降: List(3, 4) 3番目以降: List() 1個だけ

上2つは1と2から始まっているのでA、3つ目は1から始まっているのでBとなります。

「1と2から始まっているが後に何も続いていない(要素が2つだけ)の場合」もAとなり、空のリストが返っているのに注目してください。

Scala 2系よりもわかりやすくなった

実はScala 2系にも可変長引数パターンは存在していました。

今回、Scala 3へのメジャーバージョンアップに際して可変長引数パターンの記法が変更されることにより、他の文法との整合性が強化されました。

つまり、覚えるべき記号・記法が少なくなり、さらにシンプルでわかりやすくなりました。

どういうことなのか、実際に見てみましょう。

Scala 2系における可変長引数パターンの例

Scala 2系における可変長引数パターンは、以下のような書き方をしていました。

list match { case List(1, 2, xs @ _*) => ??? case List(1, 2, _*) => ??? }

サンプルコードは以下のようになります。

list match case List(1, 2, xs @ _*) => println(s"3番目以降: $xs") case List(1, _*) => println(s"1個だけ") case _ => println(s"空")

Scala 3系においてこのような書き方をすると構文エラーとなります。

関数リテラルにおいて可変長引数を表現する例

可変長引数をとるメソッドに対して可変長引数を渡すには以下のようにします。

def printVararg(args: String*): Unit = { args.foreach(println) } val vararg: Option[Seq[String]] = Some(Seq("")) vararg.map{args => printVararg(args: _*)} vararg.map{printVararg(_: _*)}

: _*という記述がありますね。
3.0以降、パターンマッチを使用する際にもこの記法によって可変長引数を表現できるようになったというわけです。
これにより、文法を覚える側としてはシンプルになりました。

たとえばうろ覚えで関数リテラルと同じような書き方をしても、2系のようにコンパイルエラーになって検索してやり方を調べたり、方法が見つからずに諦めたりといったことが減り、3系では淡い期待通りにコンパイルが通るようになったというわけです。
これは間違いなく開発者にとって嬉しい変化と言えるでしょう。

可変長引数パターンのScala 3への移行プロセスは、他の機能よりも猶予がある

さて、デフォルトでは、Scala 3系のコードにおいては3系の新しい記法を使用する必要があり、2系の古い記法は使用できないことになっています。

ただし、Scala 3のなかでも特にバージョン3.0は、2系からの移行期間に位置づけられています。

可変長引数パターンはこの「3系では3系の記法のみ」という原則から少し外れ、移行プロセスの猶予期間が長めにとられています。

具体的には以下のようになっています。

Scala 3.0では両方の書き方ができる。Scala 3.1では新しい書き方のみ使える

Scala 3系には、2系からのバージョン移行をスムーズで確実に行うためのオプションがいくつも用意されています。
Scala 3.0では以下の4つの-sourceコンパイラオプションや、scala.languageインポートオプションを使用することができます。

  • 3.0-migration
  • 3.0
  • 3.1-migration
  • 3.1

バージョン移行作業のために用意されたコンパイラオプションやインポートオプションに関して、詳しくはこちらをご覧ください。

可変長引数パターンに関しては3.0-migrationだけでなく3.0を指定した場合においても、両方の記法が使用できるようになっています。

そして、3.1-migrationを有効にすると、コンパイル時に以前の記法から:を使う新しい記法への移行を促す情報を表示することができます。

さらに、3.1オプションを有効にすると、新しい記法に限定することができます。

可変長引数パターンの移行プロセスは、いわば激変緩和措置として、他の機能に対してバージョンひとつ分だけ遅らせてあるわけです。

Scala 3において移行作業が重要視され、確実に移行できるよう細かく配慮されていることが理解できますね。

サイト内検索