- Extractor の unapply/unapplySeq で、タプルやSeqのOptionを返すのではなく、
全ての可能性を示すようなSeqを返せる様にしたい。
- Empty なら現在の None と同じ動き
- not-empty なら、先頭から順にパターンマッチを実行していき、 マッチするものが見つかったらそこでマッチの評価を終了する。
- パターン中に式を書けるようにしたい。
- 式の結果値を定数パターンとして評価する。
- 式中では、変数パターンで束縛された変数もスコープに含まれる。
参考例として、以下のようなポーカーの役を判定するプログラムを考える。
sealed trait Suite
object Suite {
case object Spades extends Suite
case object Hearts extends Suite
case object Diamonds extends Suite
case object Clubs extends Suite
val values: Seq[Suite] = Vector(Spades, Hearts, Diamonds, Clubs)
}
case class Card(number: Int, suite: Suite)
object Main extends App {
val deck: Seq[Card] = for {
n <- 1 to 13
s <- Suite.values
} yield Card(n, s)
val hand: Set[Card] = scala.util.Random.shuffle(deck).take(5).toSet
val rank = Porker.rank(hand)
println(hand)
println(rank)
}
object Porker {
object Hand {
def unapply(hand: Set[Card]): Option[(Card, Card, Card, Card, Card)] = {
implicit val suiteOrdering: Ordering[Suite] = Ordering.by(Suite.values.indexOf)
implicit val cardOrdering: Ordering[Card] = Ordering.by(unapply)
// 数字、スートを何らかの順序で一意にソートする必要がある
PartialFunction.condOpt(hand.toSeq.sorted) {
case c1 +: c2 +: c3 +: c4 +: c5 +: Nil => (c1, c2, c3, c4, c5)
}
}
}
def rank(hand: Set[Card]): String = hand match {
// ソート順を意識してパターンを記述する必要がある
case Hand(Card(1, s1), Card(10, s2), Card(11, s3), Card(12, s4), Card(13, s5)) if isFlash(s1, s2, s3, s4, s5) => "Loyal Straight Flash"
case Hand(Card(1, _), Card(10, _), Card(11, _), Card(12, _), Card(13, _)) => "Loyal Straight"
case Hand(Card(n1, s1), Card(n2, s2), Card(n3, s3), Card(n4, s4), Card(n5, s5)) if isStraight(n1, n2, n3, n4, n5) && isFlash(s1, s2, s3, s4, s5) => "Straight Flash"
case Hand(Card(n1, _), Card(n2, _), Card(n3, _), Card(n4, _), Card(n5, _)) if (n1 == n2 && n1 == n3 && n1 == n4) || (n2 == n5 && n3 == n5 && n4 == n5) => "Four of Kind"
case Hand(Card(n1, _), Card(n2, _), Card(n3, _), Card(n4, _), Card(n5, _)) if (n1 == n2 && n1 == n3 && n4 == n5) || (n1 == n2 && n3 == n5 && n4 == n5) => "Full House"
case Hand(Card(_, s1), Card(_, s2), Card(_, s3), Card(_, s4), Card(_, s5)) if isFlash(s1, s2, s3, s4, s5) => "Flash"
case Hand(Card(n1, _), Card(n2, _), Card(n3, _), Card(n4, _), Card(n5, _)) if isStraight(n1, n2, n3, n4, n5) => "Straight"
case Hand(Card(n1, _), Card(n2, _), Card(n3, _), Card(n4, _), Card(n5, _)) if (n1 == n2 && n1 == n3) || (n3 == n5 && n4 == n5) => "Tree of Kind"
case Hand(Card(n1, _), Card(n2, _), Card(n3, _), Card(n4, _), Card(n5, _)) if (n1 == n2 && n3 == n4) || (n1 == n2 && n4 == n5) || (n2 == n3 && n4 == n5) => "Two Pairs"
case Hand(Card(n1, _), Card(n2, _), Card(n3, _), Card(n4, _), Card(n5, _)) if (n1 == n2) || (n2 == n3) || (n3 == n4) || (n4 == n5) => "One Pair"
case _ => "High Card"
}
def isStraight(n1: Int, n2: Int, n3: Int, n4: Int, n5: Int): Boolean =
n1 + 4 == n5 && n2 + 3 == n5 && n3 + 2 == n5 && n4 + 1 == n5
def isFlash(s1: Suite, s2: Suite, s3: Suite, s4: Suite, s5: Suite): Boolean =
s1 == s2 && s1 == s3 && s1 == s4 && s1 == s5
}
最初にあげた2点が実現すると、以下のように書けるようになる。
この例ではパターン中の式を {}
で囲むことで表現している。
object Porker {
object Hand {
def unapply(hand: Set[Card]): Seq[(Card, Card, Card, Card, Card)] = {
// このパターンが取りうる可能性を全て列挙して返す
hand.toSeq.permutations.map {
case c1 +: c2 +: c3 +: c4 +: c5 +: _ => (c1, c2, c3, c4, c5)
}.toSeq
}
}
def rank(hand: Set[Card]): String = hand match {
// 順序を意識せずにパターンの本質だけを記述できる
case Hand(Card(10, s), Card(11, {s}), Card(12, {s}), Card(13, {s}), Card(1, {s})) => "Loyal Straight Flash"
case Hand(Card(10, _), Card(11, _), Card(12, _), Card(13, _), Card(1, _)) => "Loyal Straight"
case Hand(Card(n, s), Card({n+1}, {s}), Card({n+2}, {s}), Card({n+3}, {s}), Card({n+4}, {s})) => "Straight Flash"
case Hand(Card(n, _), Card({n}, _), Card({n}, _), Card({n}, _), _) => "Four of Kind"
case Hand(Card(n1, _), Card({n1}, _), Card({n1}, _), Card(n2, _), Card({n2}, _)) => "Full House"
case Hand(Card(_, s), Card(_, {s}), Card(_, {s}), Card(_, {s}), Card(_, {s})) => "Flash"
case Hand(Card(n, _), Card({n+1}, _), Card({n+2}, _), Card({n+3}, _), Card({n+4}, _)) => "Straight"
case Hand(Card(n, _), Card({n}, _), Card({n}, _), _, _) => "Tree of Kind"
case Hand(Card(n1, _), Card({n1}, _), Card(n2, _), Card({n2}, _), _) => "Two Pairs"
case Hand(Card(n, _), Card({n}, _), _, _, _) => "One Pair"
case _ => "High Card"
}
}
ここに更にワイルドカードとして使えるジョーカーを足すとどうなるか。
現状のScalaの文法だと考えたくないが、拡張されたExtractorがあると以下のようにするだけで、rank
メソッドは全く変えずにすむ。
sealed trait Card
object Card {
def unapply(card: Card): Seq[(Int, Suite)] = card match {
case NumberCard(n, s) => Seq(n -> s)
case Jorker => deck.collect { case NumberCard(n, s) => n -> s }
}
}
case class NumberCard(number: Int, suite: Suite) extends Card
case object Jorker extends Card
val deck: Seq[Card] = Jorker +: (for {
n <- 1 to 13
s <- Suite.values
} yield NumberCard(n, s))
計算効率の問題