Friday, May 23, 2008

Case Class in Scala

ใครที่เคยเห็น code ของ Scala, คงต้องเคยเห็นคำว่า case class
ครั้งแรกที่ผมเห็นมัน ผมรู้สึกมึนตึบ แล้วก็สงสัยว่ามันคืออะไร
ทำไม case statement มาเกี่ยวอะไรกับ class ด้วย

หลังจากนั่งอ่าน Programming in Scala ของ Martin Odersky, Lex Spoon, Bill Venners
ก็เลยเกิดความกระจ่างขึ้น
เจ้า modifier "case" ข้างหน้า class นั้น มันทำให้ compiler ช่วย generate method บางอย่างเพิ่มขึ้นมา
เพื่อทำให้เราสามารถใช้ feature pattern matching กับ Object ได้ง่ายขึ้น
ในหนังสือเขายกตัวอย่าง กรณี expression
abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr

สมัยแรกๆที่ผมเห็น statement ข้างบน ผมก็จะนึกเดาไปว่า
ตัว case class มันต้องเขียนเรียงติดๆกันแบบเดียวกับพวก case statement
แต่จริงๆแล้วไม่ใช่ "case" เป็นแค่ modifiler (เหมือนพวก public, private, static ทำนองนั้น)
ประโยคข้างบนจริงๆแล้วก็คือ การประกาศ class ขึ้นมา 5 ตัว
ที่ดูแปลกๆ ก็คือใน scala กรณี empty body, เราไม่ต้องใส่ class body { } ให้มันก็ได้ (มันจะแอบใส่ให้เราเอง)

สิ่งที่ case modifier ทำก็คือ
  1. compiler จะช่วย generate Factory method ที่ชื่อเดียวกับ class ให้เรา
    ผลก็คือ เราสามารถใช้คำสั่งแบบนี้ได้
    val v = Number(10)
    แทนที่จะเป็น
    val v = new Number(10)
    ซึ่งมีประโยชน์มากเวลาที่เรา nested แบบนี้
    BinOp("+", Var("x"), Number(7))

  2. compiler จะช่วยทำให้ argument ทุกตัวกลายเป็น read only property ของ class นั้น
    เช่น

    scala> val b = BinOp("+", Var("x"), Var("x"))
    b: BinOp = BinOp(+,Var(x),Var(x))

    scala> b.operator
    res23: String = +

  3. compiler จะช่วย generate พวก method equals, hashcode ให้เรา
    โดยตัว equals method มันจะ implement ในลักษณะที่จะเปรียบเทียบทุกๆ property ของ object ให้เรา
    ทำให้เราสามารถเปรียบเทียบแบบนี้ได้

    scala> Var("x") == Var("x")
    res21: Boolean = true

    scala> BinOp("+", Number(7), Number(8)) == BinOp("+", Number(7), Number(8))
    res22: Boolean = true



ดูเหมือนว่าสิ่งที่มัน gen ก็ไม่มีอะไรมาก
แต่ผลที่ได้ทำให้เราสามารถทำ pattern matching แบบนี้ได้
def simplifyTop(expr: Expr): Expr = expr match { 
case UnOp("-", UnOp("-", e)) => e // Double negation
case BinOp("+", e, Number(0)) => e // Adding zero
case BinOp("*", e, Number(1)) => e // Multiplying by one
case _ => expr
}

Related link from Roti