ScalaのExtractorを検討する

3864 ワード

このコンセプト(Extractor)に初めて触れるのは、ちょっと理解しにくいですが、本人が英語が通じないのかもしれません.推敲を重ねた結果、何が起こっているのかやっと分かった.やはり一つの例から言えば.
文字列のフォーマットがメールアドレスのフォーマットに合致するかどうかを検証したいとします.もしそうであれば、ユーザー部分とドメイン名部分を抽出します.たとえば、指定された文字列"[email protected]」と、テストによりメールアドレスのフォーマットに合致することを発見し、「jack」と「163」を抽出する.com".一般的には、正規表現でマッチングを行い、対応するマッチンググループを抽出する.
Scalaではモードマッチングにより実現できる.具体的には、オブジェクト(objと呼んでもよい)にunapplyメソッドを定義し、このメソッドはOption[T]タイプを返さなければならない.オブジェクトを指定したパラメータ(selectorと呼んでもよい)とモードマッチングすると、オブジェクトのunapplyメソッドが呼び出されます.unapplyメソッドを呼び出すと、一致するパラメータ(selector)が入力されます.マッチングロジック定義unapplyメソッドでは、マッチングに成功した場合、unapplyメソッドはSome[T]タイプのオブジェクトを返し、そうでなければNoneを返します.Some()に含まれるコンテンツには制限はありませんが、一致するコンテンツを持ち帰るのが一般的です.
たとえば
object Email{
    def unapply(str: String): Option[(String, String)] = {
        val parts = str split "@"
        if (parts.length == 2) Some(parts(0), parts(1)) else None
    }
}

val str = "[email protected]" 
str match{
   case Email(username, address) =>  println("username: "+username+" address: "+addres);
   case _ =>  println("this is not an email address ");
}

実際にstrがEmailとモードマッチングすると
Email.unapply(str) match{
   case Some(username, address) =>  ...
   case None => ...
}

extractorとは、unapplyメソッド(またはunapplySeqメソッド)を含むオブジェクトを指す. 
文字列が合法的なメールアドレスであるかどうかを確認したい場合は、unapplyにBooleanタイプを返すことができます. 
メールのユーザー名に大文字が含まれておらず、重複する2つの部分から構成されている場合は、たとえば[email protected]ああ、そうすることができます.
object LowerCase{
   def unapply(name: String) = name.toLowerCase == name
}

object Twice{
    def unapply(s: String) : Option[String] = {
        val len = s.length /2 
        val half = s.substring(0, len);
        if (half == s.substring(len)) Some(half) else None
    }
}

def userTwiceLower(s: String) = s match{ 
    case Email(Twice(x @ LowerCase()), domain) => "match: "+ x + " in domain " + domain
     case _ => "no mach"
}

 
userTwiceLower("[email protected]")
userTwiceLower("[email protected]")
userTwiceLower("[email protected]")

もちろん正規表現で実現できますが、それはずっと複雑で柔軟性に欠けていると信じています. 
selectorの複数のコンポーネントをマッチングまたは分解する必要があり、事前にどれだけあるか分からない場合は、unapplySeqメソッドを使用します.unapplySeqの使い方はunapplyとあまり差がありませんが、Option[Seq[T]]タイプを返さなければなりません.例えば私は[email protected]のメールドメイン名の各構成部分を抽出し,cn,edu,gdut.次の例は、メールユーザー名の接頭辞がstu_であることを示しています.ドメイン名cnのメールアドレス:
object Email { ... }
 
object Domain{
    def unapplySeq(whole: String) : Option[Seq[String]] ={
        Some(whole.split("\\.").reverse);
    }
}

object StuPrefix{
    def unapply(name: String) = name.startsWith("stu_")
}

def isStuCnMail(str: String) = str match{
    case Email(StuPrefix(), Domain("cn", _*)) => true
    case _ => false
}

isStuCnMail("[email protected]")

もちろんunapplyを使ってもいいですが、書き方はそんなに直感的ではありません. 
実はScalaのArray,Listなどの集合クラスはunapplySeqメソッドを実現し,このように書くことができる.
val Array(a,b,c,d) = Array(1,2,3,4)
val List(head, tail @ _ *) = List(1,2,3,4)  

Constructor Patternに似ているように見えますが.
extractorに対する理解が「ユーザー定義のパターンマッチングが可能」という技術的な面にとどまっている場合は、あまりにも浅いと思います.ScalaがExtractorという文法糖を提供する目的は、データモデルとビューを論理的に分離したり、アダプタのような役割を果たしたりして、関数式を比較することだと思います.
実際のプログラミングでcase classを採用すべきかextractorを採用すべきかについては、公式の提案は次のとおりです.
     1. 定義したデータ構造やインタフェースが内部使用に限られ、頻繁に変更されない場合はcase classを推奨します.
     2. インタフェースが他の人に使用されている場合、またはいくつかの残留クラスに直面している場合は、extractorを使用することをお勧めします. 
     3. 間違っている場合はcase classを採用し、case classが需要の変化に適応できないことを発見したらextractorを変更することができます.
case classを使用してモードマッチングを行うメリットは、コンパイラがコードを最適化できることです.もしあなたのcase classがパッケージクラス(sealキーワードで修飾されたクラス)から継承されている場合、コンパイラはmatch式をチェックし、可能性のある状況を漏らしたかどうかを注意することもできます.extractorは柔軟で、あなたが望むマッチングロジックを実現できますが、case classよりも実行効率が遅いです.