iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 25
0
Big Data

Spark 2.0 in Scala系列 第 25

[Spark-Day25](Scala番外篇) Extrator、Case Class、Sealed Class大亂鬥

看完最直接的pattern match基礎應用,稍微講一些周邊作為番外的Ending吧,常常與pattern match一起提到的概念大概有Extrator、Case Class、Sealed Class、Enum、Option等等。來看看到底是啥回事吧~


[Snippet.55] patten with Regex
開始之前,先來個patten match小複習。
patten match跟還能搭配Regex,如何!

val pattern = "([0-9]+) ([a-z]+)".r ①
"99 bottles" match {
  case pattern(num, item) => println("num=" + num + ", and item =" + item) ②
  case _ => "anything else"
}

①在Scala中怎麼宣告一個Regex物件勒?String.r就是這麼簡單
②抽出兩個比對到的元素,分別放入numitem

Extractor

如果仔細想一下,就會覺得昨天那些集合的match很厲害,為啥case Array(0, z)case x :: y => x + " " + ycase (_, 0) => "..0"可以抓出值來配對勒??因為有所謂的Extractor。Extractor的簡單定義是Object with an unapply method,舉個簡單的範例吧:

object Twice ①{
  def apply(x: Int): Int = x

  def unapply(z: Int): Option[Int] = ②
    if (z % 2 == 0) Some(z / 2) else Some(100)
}

①將Twice宣告為object(singleton)
②建立unapply method
apply的概念是將參數帶入以產生物件,而unapply當然就是將物件帶入,抽出值啦~

有了Twice物件後,寫個match吧:

def twiceMatch(twice: Any): Any =
  twice match {
    case Twice(n) => n
  }
twiceMatch(Twice(20))
twiceMatch(Twice(21))

這樣輸出是啥勒?

//Output:
10
100

答對了嗎?

Case Class

case居然能變成class!!!XDD 別懷疑,直接上官方文件的說明吧!

Scala supports the notion of case classes. Case classes are just regular classes that are:

  • Immutable by default
  • Decomposable through pattern matching ①
  • Compared by structural equality instead of by reference ②
  • Succinct to instantiate and operate on

①就代表case Class已經自帶apply, unapply了啦,不用自己寫
②equals、hashCode也做好了可以直接比較
另外case Class也實作了簡單的toString,因為是immutable的,所以還有做一個copy方便複製,等等來玩玩。除此之外,與一般Class無異,他可以繼承也可以被繼承。
我的感覺是Case Class跟Java POJO有點像,這類class都不會太複雜,拿來封裝比較用的。


先寫幾個簡易的 Case Class玩玩吧:
[Snippet.56] Case Class

abstract class Amount ①
case class Dollar(value: Double) extends Amount ②
case class Currency(value: Double, unit: String) extends Amount ③
case object Nothing extends Amount ④

①一個名為Amount的Abstract Class
②繼承Amount的Case Class,宣告方式跟一般Scala Class差不多,也能繼承別人~
③另外一個Case Class
④Case Class也能宣告成Object

建好後來玩玩吧:

object AmountTest extends App {
  val currency = Currency(20, "NT")  ①
  println(currency.value)
  println(currency.unit)
  
  
  //immutable
  //currency.unit = "US"; ②
  val anotherDollar = currency.copy(unit = "US") ③
  println(anotherDollar.value)


  def amtMatch(amt: Amount): Any = ④
    amt match {
      case Dollar(value) => "$" + value ⑤
      case Currency(value, unit) => value + ", and unit is " + unit ⑥
      case Nothing => ""
    }

  println(amtMatch(Dollar(20)))
  println(amtMatch(Currency(30, "NT")))
  println(amtMatch(Nothing))
}

①宣告一個Currency的Case Class,並且印出他的field member
②這些field都是val(immutable),如果修改會報Error喔(reassignment to value)
③如果我只想小修一個欄位,例如換掉Currency的單位勒,你可以透過copy方式來修改
④進入pattern match吧!
⑤ 此時Case的對象就是case Class的物件囉,可以寫成Dollar(value)Currency(value, unit)因為Case Class已經內建unapply啦~

輸出:

20.0
NT
20.0
$20.0
30.0, and unit is NT

之前在[Snippet.41]建立updateSateByKey時,func裏面有用到Some跟Null搭配pattern match還記得嗎?

def func(
   vals: Seq[(Double)],preValue: Option[Double]): Option[Double] =  ①
preValue match { ②
    case Some(total) => Some(vals.sum + total) ③
    case None => Some(vals.sum) ④
  }

現在應該可以推測的出來Some跟None是啥了吧!沒錯,也是Case Class!

final case class Some[+A](x: A) extends Option[A] {
  def isEmpty = false
  def get = x
}

case object None extends Option[Nothing] {
  def isEmpty = true
  def get = throw new NoSuchElementException("None.get")
}

Infix notation

在deconstruct或是在用遞迴拆解List的時候,常常會冒出list.head::list.tail這樣的東西,為啥可以寫成這樣勒?::又是啥阿~其實這一切都是infix的功能啦!
當一個case class有兩個parameter時,你在pattern match中可以使用infix的方式代表class,也就是 paraA + ClassName + paraB這種乍看之下很奇怪,但有時候反而比較直覺的寫法,舉個例吧:

case class And(val val1: Int, val val2: Int) ①

object InfixTest extends App {
  val obj = And(3, 4) ②
  obj match {
    case 3 And 4 => println("haha") ③
    case _ => println("nothing")
  }}

①宣告有兩個參數(parameter)的Case Class
②初始化一個Case Class物件取名為obj
③在patten match中用infix方法表示obj

所以之前的amtMatch也能用infix-style改寫Currency(因為他有兩個參數阿~)

 def amtMatch2WithInfix(amt: Amount): Any =
    amt match {
      case Dollar(v) => "$" + v
      case value Currency unit => value + ", and unit is " + unit ①
      case Nothing => ""
    }

①改成value在前,單位在後,ClassName居中的表示方式。

所以其實拆解List時用的::根本就是一個Case Class阿XD好妙,看看原始碼吧!

final case class ::[B](override val head: B, private[scala] var tl: List[B]) ① extends List[B] {
  override def tail : List[B] = tl
  override def isEmpty: Boolean = false
}

看到①有沒有,參數就是一個泛型B的變數跟一個List[B],然後body中tl指給tail,所以就可以寫成head::tail啦。

Sealed Class

有時候你在列舉case class怕會遺漏,可以在他的superclass上面注明sealed,拿直接的範例來改就會變成:

sealed abstract class Amount ①
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case class AnotherCurrency(value: Double, unit: String) extends Amount ②
case object Nothing extends Amount

①加上sealed標記的superClass,這樣要注意seal Class與其Case Class要在同一個檔案中。這點比較特別
②額外亂加一個先前patten match中沒有的Case Class。

若重新compile則會跳出WARN:

Warning:(30, 5) match may not be exhaustive.
It would fail on the following input: AnotherCurrency(_, _)
    amt match {

不想看到WARN可以在最後面加上_或是@unchecked Annotation標記告知compiler。


Case Class還能幹嘛勒?還能拿來當作Enum使用!,例如:

sealed abstract class TrafficLight
case object Red extends TrafficLight
case object Yellow extends TrafficLight
case object Green extends TrafficLight

object EnumSimulationTest extends App {
  val color: TrafficLight = Red
  color match {
    case Red => println("The color is red")
    case Yellow => println("The color is yellow")
    case Green => println("The color is Green")
  }
}

當然Scala也有Enumeration helper可以用,看看一般的Enum怎麼寫吧:

object TrafficLight extends Enumeration {
  type TrafficLight = Value
  val Red = Value
  val Yellow = Value(20, "red")
  val Green = Value("green")
}

簡單說一下Enum,基本上每個Enum元素都由(ID,value)構成。
Red啥都沒寫,因為ID預設為0開始遞增,所以Red的ID為0。value沒宣告就是等於名稱(Red)。Yellow的ID為20,值為"red"XD。Green沒宣告ID只有宣告value,ID就會從前一個(Yellow.id)遞增1,所以是21。

可以用for express觀察

for (c <- TrafficLightColor.values) println(c.id + " : " + c)
//Output:
//0 : Red
//20 : red
//21 : green

上一篇
[Spark-Day24](Scala番外篇) Patten matching
下一篇
[Spark-Day26](Spark 好友篇) Streaming with Kafka初探
系列文
Spark 2.0 in Scala30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言