探讨Scala的Extractor

     初次接触这个概念(Extractor),有点不好理解,可能是本人英语不过关。经过反复推敲,总算弄明白是什么一回事. 还是从一个例子说起。

     假设我们想验证一个字符串的格式是否符合邮件地址格式,如果是,提取它的用户部分和域名部分。比如给定字符串"jack@163.com" , 经过测试发现符合邮件地址格式,然后提取出"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 = "jack@163.com" 
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类型. 

        假如你希望过滤出邮件用户名不含有大写字母,  且是由重复的两部分组成的,比如coco@hotmail.com,你可以这样做: 

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("coco@hotmail.com")
userTwiceLower("COco@hotmail.com")
userTwiceLower("ggd543@gmail.com")

        当然你完全可以用正则表达式实现,但我相信那样做要复杂得多,而且缺乏灵活性。 

        假如你需要匹配或分解selector的多个组成部分, 而事先又不确定有多少,你可以使用unapplySeq方法。unapplySeq的用法跟unapply差不多,但必须返回Option[Seq[T]]类型。 比如我需要把ggd543@gdut.edu.cn的邮件域名各组成部分提取出来, 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("stu_jack@gdut.edu.cn")

      当然使用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要慢。

      

      

      

 

 

转载于:https://my.oschina.net/aiguozhe/blog/38348

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值