- 2014/10/25 函数型なんたらの集い 2014 in Tokyo
- @gakuzzzz
- 中村 学
- 株式会社Tech to Value
- Scalaから来ました
https://twitter.com/bleis/status/512053257351348224 https://twitter.com/gakuzzzz/status/512071190035173377
特に依存しているわけではありません。
パターンマッチを持たない函数型言語もあれば、パターンマッチを持つ非函数型言語もあるでしょう。
パターンマッチが無ければ函数型言語ではない、とも言えません。
でも親和性高いし、なんだかんだで函数型言語では古くから一般的なので、同一コンテキストで語られることが良くあります。
こんなの
val userId: Option[Int] = Some(100)
val result = userId match {
case Some(id) => s"user: $id is logged in"
case None => s"not be logged in"
}
println(result) // user: 100 is logged in
def duplicate(a: Int): (Int, Int) = (a, a)
val (foo, bar) = duplicate(10)
println(foo) // 10
println(bar) // 10
val list = Seq(Some(10), None, Some(3), Some(5), None)
val result = for {
Some(value) <- list
} yield value * 2
println(result) // List(20, 6, 10)
- ネストした複雑な条件を簡単に表現
- 値の判定と取得を不可分に
赤黒木の rotate の例
import tree.{Node => N, Red => R, Black => B}
private val balance: (Color, RBTree, Entry, RBTree) => Node = {
case (B, N(R, N(R, a, x, b), y, c), z, d) => N(R, N(B, a, x, b), y, N(B, c, z, d))
case (B, N(R, a, x, N(R, b, y, c)), z, d) => N(R, N(B, a, x, b), y, N(B, c, z, d))
case (B, a, x, N(R, N(R, b, y, c), z, d)) => N(R, N(B, a, x, b), y, N(B, c, z, d))
case (B, a, x, N(R, b, y, N(R, c, z, d))) => N(R, N(B, a, x, b), y, N(B, c, z, d))
case (c, l, e, r) => N(c, l, e, r)
}
よく見る Java7 以前の例
final Optional<String> userNameOpt = Optional.from("gakuzzzz");
if (!userNameOpt.isPresent()) return;
final String userName = userNameOpt.get(); // こわい
仮にメソッドが長くなって分割しよう、とかした時に、うっかり if
と get()
を分けてしまい、if
通らずに get()
の処理に流れるバグを仕込んでもコンパイル通ってしまいます。
_人人人人人人人人人人人人人人人人_
> 実行時NoSuchElementException <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
パターンマッチの場合は値の判定と取得が不可分なので、この手のうっかりミスを入れようがありません。
Scalaではユーザがパターンを独自に追加できます。
DateTime
から年と月と日を取得する例
object YMD {
def unapply(d: DateTime): Option[(Int, Int, Int)] = {
Some((d.getYear, d.getMonth, d.getDayOfMonth))
}
}
def dateString(d: DateTime): String = d match {
case YMD(_, 12, 25) => "クリスマス",
case YMD(2014, 10, 25) => "函数型なんたら2014"
case _ => "ふつーの日"
}
値を取り出す個数が不定の場合は、タプルの代わりに Seq
が使える unapplySeq
というメソッドでもOKです。
Scala ではこういう unapply/unapplySeq を持ったオブジェクトを Extractor と呼んだりします。Constructor との対比ですね。
Seq
のパターンマッチでよく使われる +:
も +: オブジェクトが unapply を持っている ので書くことができます。
def sum(seq: Seq[Int]): Int = {
seq match {
case head +: tail => head + sum(tail)
case _ => 0
}
}
面白い実例としては、 unfiltered というライブラリが提供している &
という Extractor です。
object & {
def unapply[A](a: A): Option[(A, A)] = Some((a, a))
}
これは単純に評価するオブジェクトを複製して返します。
これがあると、複数のパターンにマッチするパターンというのが簡単にかけるようになります。
def dateString(d: DateTime): String = d match {
case YMD(2014, 10, _) & HMS(15, 20, _) => ...
...
}
GHC 構文拡張の View Patterns を使うことで、似たような事を実現できます。
{-# LANGUAGE ViewPatterns #-}
import Data.Time.Calendar
dateString :: Day -> String
dateString (toGregorian -> (_, 12, 25)) = "クリスマス"
dateString (toGregorian -> (2014, 10, 25)) = "函数型なんたら2014"
dateString _ = "ふつーの日"
Clojure だと core.match が標準でありますが、これはユーザが独自にパターンを追加することができません(よね?間違ってたら指摘頂けると助かります)。
そこで、ねこはる先生が unapply というライブラリを作成されました。
このライブラリを使うと、ユーザ定義のパターンを使ってパターンマッチができるようになります。
(defn ymd [d]
[(year d) (month d) (day d)])
(defn date-string [d]
(match d
(ymd _ 12 25) "クリスマス"
(ymd 21014 10 25) "函数型なんたら2014"
_ "ふつーの日"))
ソースコード上でパターン表現する関係上、どうしても順序が生まれてしまう、という問題があります。
例えば、要素の順序に意味を持たない Map[K, V]
みたいなデータ構造で、パターンマッチを行いたい時を考えてみてください。
object M {
def unapplySeq[K, V](map: Map[K, V]): Option[Seq[(K, V)]] = Some(map.toSeq)
}
// 意図した通りには動かない!
def test(map: Map[Int, String]): String = map match {
case M((_, v1), (_, v2), _:*) if v1 == v2 => "2つのkeyで同じ値を持っている"
case M((10, _), _:*) => "10というkeyを持っている"
case _ => ""
}
みたいな事をやりたいけれど、Mapが順序に意味を持たないため、単純に toSeq
をしてもどういう順序で並んでいるか予想がつきません。
また、仮に key が Int などの順序付けできる型だとして、結果の Seq を key の順でソートできたとしましょう。
その場合でも、任意の二つの key が同じ value を持つ、というのを現在のパターン上で表現するのは難しいです。
def test(map: Map[Int, String]): String = map match {
case M((_, v1), (_, v2), _:*) if v1 == v2 => ...
case M(_, (_, v1), (_, v2), _:*) if v1 == v2 => ...
case M(_, _, (_, v1), (_, v2), _:*) if v1 == v2 => ...
...
}
Clojure は Map に対するパターンマッチを標準で持っていて便利ですね。
こういった順序が意味を持たないデータに対して、うまくパターンマッチを書けるようにした言語があります。
直感をそのまま表現するパターンマッチング
Egison は1つの定まった標準形を持たないデータに対しても柔軟なパターンマッチが表現可能なプログラミング言語です。 リストや多重集合、集合、ツリー、グラフなどといった幅広いデータ型に対して、パターンマッチが記述できます。 それにより、Egison プログラマは非常にシンプルにプログラムを記述できるようになります。
Egison で面白いところは、パターンがマッチした結果として、複数の結果が返ってくる場合も考えられている点ですね。
くやしいので、Scalaでも似たような事を実現できるような文法拡張を考えてみました。
ニーズが多ければ本家にも積極的に提案していきたいと考えてるので、フィードバックその他ご意見頂けるとうれしいです。
パターンマッチは適した場所で使うとコードを簡潔にして可読性、保守性を上げることができます。
上手に使ってハッピーなエンジニアライフを送りましょう。