pattern matchingを使うためにはcase classを用意する必要がある。
case classを作らないでpattern matchingを使いたいかもしれない。
独自のpatternを作りたいかもしれない。
Extractorsを使えばpatternを定義してobjectから取り出すことが出来る。
24.1 An example: Extracting email addresses
例としてemail addressのユーザ部分とドメインの切り出しを考える。
アクセス関数を使って行う場合。
def isEMail(s: String): Boolean
def domain(s: String): String
def user(s: String): String
if (isEMail(s)) println(user(s) +" AT "+ domain(s))
else println("not an email address")
ちゃんと動くけど無駄が多い。
複数の条件を組み合わせるともっと複雑になりイマイチ。
pattern matchingが使えるとしたら。
ss match {
case EMail(u1, d1) :: EMail(u2, d2) :: _ if (u1 == u2) => ...
...
}
シンプルに書ける。
問題はStringがcase classではないのでpattern matchingが使えない事。
EMail(user, domain)の様な内部表現を持っていない。
Extractorを使えば出来る。
既存のtypeに対して新しいpatternを定義出来る。
typeの内部表現は不要。
24.2 Extractors
unapply methodを持つobject
値のmatchingと分離を行う。
apply methodを持つこともある。(optional)
値を構築するためのmethod。
** companion objectと似ている。
email addressのExtractorの例
object EMail {
// The injection method (optional)
def apply(user: String, domain: String) = user +"@"+ domain
// The extraction method (mandatory)
def unapply(str: String): Option[(String, String)] = {
val parts = str split "@"
if (parts.length == 2) Some(parts(0), parts(1)) else None
}
}
apply method(injection)を持つ場合function typeを明示的にextendsしても良い。
object EMail extends (String, String) => String { ... }
これはFunction2[String, String, String]と同じ。
?? the declaration of the abstract apply method ??
unapplyはSome(x)を返す。matchしないときはNone
?? selectorとunapplyの引数のtypeが合わなくても使える??
?? unapplyの引数のtypeにcastしてmatchされる??
injection methodがあってもobjectはExtractorと呼ばれる。
applyとunapplyは対にすべき。
特に制約がある訳ではないが、分かり易さのため。
24.3 Patterns with zero or one variables
3個以上に分離する場合は3以上の要素数のtupleをSome()に包んで返せばよい。
1個と0個の時は扱いが少し違う。
1個の場合
tupleは使わずに値を直接Some()に包んで返す。
1要素のtupleはない
0個の場合
()のみの引数無(Unit??)にmatchさせる。UpperCase()
()は省略できない。省略するとobjectを値としてmatchさせようとしてしまう。
値は分離できないが、matchした全体をbindingは出来る。
x @ UpperCase()
24.4 Variable argument extractors
可変長要素を取り出したいとき。
List(x, y , _*)と同様な事がやりたい。
unapplySeq methodを使う。
object ExpandedEMail {
def unapplySeq(email: String)
: Option[(String, Seq[String])] = {
val parts = email split "@"
if (parts.length == 2)
Some(parts(0), parts(1).split("\\.").reverse)
else
None
}
}
可変数要素のみの場合はOption[Seq[T]]を返す。
固定数要素 + 可変数要素の場合は、Option[T1, T2, Seq[T3]]
T1, T2, Seq[T3] のtupleをSome()で包んだもの。
bindingも可能
case ExpandedEMail("tom", domain @ _*) => ...
24.5 Extractors and sequence patterns
ListやArrayのsequence patternsも実体はExtractor
ライブラリでunapplySeq()を実装している
24.6 Extractors versus case classes
Extractorの利点
実装非依存(Representation independence)
patternがtypeのデータ表現に依存していない。
typeの実装を後から変更してもclient codeへの影響を抑えられる。
unapply methodの作り次第でどんなpatternでも作れる。
case classの利点
シンプルで分かりやすい。
性能が良い。特定のパターンに絞ってコンパイラが最適化出来るようにしている。
全パターン網羅されているかコンパイラでチェック出来る。
まずはシンプルなcase classでの実装を検討してみる。
case classから後でExtractorに変える事も出来るので。
Extractorにした方が良いのは以下の時。
case classでは扱えないpatternを使いたいとき。
データ表現とpatternが合わない場合等。
ユーザが不特定多数で、後からデータ表現が変わる可能性があるとき。
case classだとデータ表現の影響で使えるpatternが変わってしまう。
ユーザが使用するpatternを制限出来るならcase classでも良い。
24.7 Regular expressions
Extractorの有効な使い方として正規表現がある。
scalaはJavaの正規表現を継承していて、Javaは大体Perlと同じ。
java.util.regex.Pattern 参照
正規表現の生成方法。
import scala.util.matching.Regex
new Regex("(-)?(\\d+)(\\.\\d*)?")
\はエスケープなので\\にする。
new Regex("""(-)?(\d+)(\.\d*)?""")
raw stringsを使えば\のエスケープ不要。
"""(-)?(\d+)(\.\d*)?""".r
Stringに .r methodを適用しても生成出来る。
StringがRichStringにimplicit conversionされて .r が適用される。
検索方法
regex findFirstIn str
最初にマッチした部分文字列のOption typeを返す。
regex findAllIn str
マッチする全ての部分文字列のIteratorを返す。
regex findPrefixOf str
strの先頭からマッチさせた部分文字列のOption typeを返す。
Extracting with regular expressions
全ての正規表現はExtractorにもなっている。
正規表現の(), groupの部分を切り出せる。
Perlの$1,$2...と同様。
matchしなかったgroupにはnullがbindされる。
for expressionに正規表現のExtractorを使う事も出来る。
24.8 Conclusion
Extractorを使えばtypeのデータ表現とは独立して柔軟にpatternを定義出来る。
大規模ソフトではpatternをデータ表現と分離できることも保守性の向上で重要。
Extractorは柔軟なライブラリの抽象化を可能にする。
scalaのライブラリでも、正規表現の様に、多用されている。
0 件のコメント:
コメントを投稿