Scalaパターンマッチの Infix Operation Pattern
Unfiltered を使うときに必要になるのですが、キモ便利です
ちょっとテストデータを定義しますね。
case class Person(name: String, age: Int, height: Int) val tanaka = Person("tanaka", 16, 170)
田中君は16才で170cmです。
田中君は case class として定義しました。
case class なので、みんな大好きパターンマッチで分解してフィールドを取り出すことができます。
tanaka match {
case Person(name, _, _) => println(name)
}
普通ですね。
で、実はこんなことができます。
tanaka match {
case name Person (_, _) => println(name)
}
なにがなんだかわからないかもしれませんが、これが パターンマッチの Infix Operation Pattern です
説明します
言語仕様の該当箇所はここ
8.1.10 Infix Operation Patterns Syntax: Pattern3 ::= SimplePattern {id [nl] SimplePattern} An infix operation pattern p op q is a shorthand for the constructor or extractor pattern op(p, q). The precedence and associativity of operators in patterns is the same as in expressions (§6.12). An infix operation pattern p op (q1, ..., qn) is a shorthand for the constructor or extractor pattern op(p, q1, ..., qn).
以上。
便利な使い方
前述の田中君の例ではだからどうした感が強いですが、 Unfiltered ではこれを有効に使っています。
「&」 っていうExtractor を1つ定義しているだけですが、劇的に便利です。
object & { def unapply[A](a: A) = Some((a, a)) }
たった一行ですが、これによって、パターンマッチされる対象を"分身"させることができます。
scala> 1 match { case &(a, b) => println((a, b)) } (1,1) scala> 1 match { case a & b => println((a, b)) } (1,1)
さらにパターンマッチの再帰的にマッチする性質を組み合わせるといくつもつなげられる!
つまり a & b & c ...ができます。
a & b が &(a,b) だから
a & b & c は &(a, b & c) で &(a, &(b, c)) !ほらマッチした!
この「&」具体的には、例えばGETリクエストからパスと、クエリパラメータを取り出すとこで使います。
case GET(UFPath("/even") & Params(params)) =>
便利な使い方その2
テストデータです。
abstract sealed class Nationality case object Japan extends Nationality case object US extends Nationality case class Person(name: String, age: Int, admin: Boolean, nationality: Nationality) val tanaka = Person("tanaka", 16, true, Japan) val satou = Person("satou", 20, false, Japan) val inoue = Person("inoue", 23, true, Japan) val john = Person("john", 25, true, US)
名前 | 年齢 | 管理者 | 国籍 |
田中 | 16 | true | 日本 |
佐藤 | 20 | false | 日本 |
井上 | 23 | true | 日本 |
ジョン | 25 | true | アメリカ |
田中君、佐藤君、井上君、ジョン君の中から、
- 年齢が18以上で
- admin権限があって
- 日本人
の人を探そうと思います。
List(tanaka, satou, inoue, john) foreach { p => p match { case Person(name, age, true, Japan) if age >= 18 => println(p) case _ => () } } /* 結果: Person(inoue,23,true,Japan) */
あ、パターンマッチが便利すぎてさらっとできてしまいました。。。
で、でももっと条件が複雑だったらガード部分が複雑になったりするかもしれないなあとか
あるかもしれないです。あるんです。
てなわけで、ちょっと違ったアプローチをしてみましょう。
自前で細かい Extractor をいろいろ定義します。
Extractor というとその名前のとおり、なにかを抽出するのに使われますが、
ここでは検索条件のような使い方をします。
// 18 才以上にマッチ object R18 { def unapply(p: Person) = { p match { case Person(_, age, _, _) if age >= 18 => Some(p) case _ => None } } } // Admin だったらマッチ object Admin { def unapply(p: Person) = { p match { case Person(_, _, true, _) => Some(p) case _ => None } } } // 日本人にマッチ object Japanese { def unapply(p: Person) = { p match { case Person(_, _, _, Japan) => Some(p) case _ => None } } }
では、再び
- 年齢が18以上で
- admin権限があって
- 日本人
を探そうと思います。
tanaka :: satou :: inoue :: john :: Nil foreach { person => person match { case R18(_) & Admin(_) & Japanese(_) => println(person) case _ => () } } /* 結果: Person(inoue,23,true,Japan) */
あまり便利そうに見えないかもしれませんが、こういうこともできるんですよ、ということで。
そしてここまで書いて思いついた。これ、パターンマッチの Pattern Alternatives (|) と組み合わせたらなんかカッコイイんじゃなイカ?
- 年齢が18以上でadmin権限がある
または
- admin権限があって、日本人
って条件で探してみよう。
tanaka :: satou :: inoue :: john :: Nil foreach { person => person match { case (R18(_) & Admin(_)) | (Admin(_) & Japanese(_)) => println(person) case _ => () } } /* Person(tanaka,16,true,Japan) Person(inoue,23,true,Japan) Person(john,25,true,US) */
できた!
まとめ
というわけで Infix Operation Pattern と、使用例でした。
パターンマッチはよいものだ。