Scala ADT关注点

Overview

ADT(Algebraic Data Type, 代数数据类型),是函数式编程(FP)构建组合类型的方式。

编程语言会内置一些基本类型(Primitive Data Types, Byte, Short, Int, Long, Float, Double, Boolean, Char),但是基本类型只能表示非常简单的值,无法对复杂的现实世界建模,因此编程语言还会提供将基本类型组合成复合类型

不同编程语言提供了不同的构建组合类型的机制,例如,常见于C++,java的是结构体struct,类class,而在scala里面就是ADT。

代数模型有三类product、sum和Hybrid,常见是前两类,

  • product,是一种组合,即A和B,如Tuple(), trait Function2()
    • scala可以用case class构建product类型
    • 两种类型是同时存在的
    • 如,
        case class Pair (
            x: Int,
            y: Int
        )
      
  • sum,是一种选择,即A或者B,如Option(只能是Some or None)
    • scala通过sealed trait + case object/class构建sum类型
    • sealed
      • 其中sealed用于行程闭环,类型的定义已经限定了所有可能性,不会出现其他可能性了
      • sealed自身及其子类只能在同一.scala文件下被继承
    • case object/ case class
      • object可以提供单例
      • case提供了unapply()自动解构函数,便于match
      • 自动重写了equals,hashCode,toString
      • 自动扩展了Serializable
    • 如,
        // 方向例子(单例)
        // 这里当然也可以用case class来实现非单例的形式,即hybrid的模式
        sealed trait Direction
        case object North extends Direction
        case object South extends Direction
        case object East extends Direction
        case object West extends Direction
              
        // 布尔例子(单例)
        sealed trait Bool
        case object True extends Bool
        case object False extends Bool
      
  • hybrid,是product和sum的组合,即Sum of Products
    • 如,
        // 非单例模式,这里用了case class
        sealed trait Shape // Sum(限定只能是圆形`或`矩形两类)
        final case class Circle(radius: Double) extends Shape // Product
        final case class Rectangle(width: Double, height: Double) extends Shape // Product
      

image

sealed自身及其子类只能在同一.scala文件下被继承

用途

ADT最大的价值是用于模式匹配,即解构一个对象。

解构对象之后,就可以避免使用很多if-else来组织分支,而是直接使用case来解出。

// 代数数据类型
// 主要希望利用一些代数的性质
// 常见的代数类型是product和sum
// ADT最大的价值是用于`模式匹配`,即解构一个对象
object ADT {

  // example 0
  sealed trait Tree // sealed闭环
  case class Node(left: Tree, right: Tree) extends Tree

  case class Leaf[A](value: A) extends Tree

  // example 1
  sealed abstract class Coupon {
    val id: Long
    val discount: Double
    val status: Boolean
  }

  case class CashCoupon(id: Long, discount: Double, status: Boolean = false) extends Coupon

  case class DiscountCoupon(id: Long, discount: Double, status: Boolean) extends Coupon

  case class GiftCoupon(id: Long, discount: Double, status: Boolean) extends Coupon

  case class GroupCoupon(id: Long, discount: Double, status: Boolean) extends Coupon

  sealed trait CouponStatus {
    val base: CouponStatusBase //每种状态共用的一些信息
  }

  case class CouponStatusBase(coupon: Coupon)

  case class StatusNotFetched(base: CouponStatusBase) extends CouponStatus //未领取
  case class StatusFetched(base: CouponStatusBase, user: Int) extends CouponStatus //已领取但未使用
  case class StatusUsed(base: CouponStatusBase, user: Int) extends CouponStatus //已使用
  case class StatusExpired(base: CouponStatusBase, user: Int) extends CouponStatus //过期优惠券
  def pay(status: CouponStatus): Unit = status match {
    case StatusNotFetched(base) => println(s"base=$base")
    case StatusFetched(base, user) => println(s"base=$base, user=$user")
    case StatusUsed(base, user) => println(s"base=$base, user=$user")
    case StatusExpired(base, user) => println(s"base=$base, user=$user")
  }

  // example 2
  sealed trait PaymentMethod

  case class Cash() extends PaymentMethod

  case class Cheque(chequeNumber: String) extends PaymentMethod

  sealed abstract class CardType // trait或者abstract class都行
  case class Visa() extends CardType

  case object MasterCard extends CardType // object或者case class都行
  case class Card(cardType: CardType, cardNumber: String) extends PaymentMethod

  def payMe(method: PaymentMethod): Unit = method match {
    case Cash()  println("Pay with cash")
    case Cheque(checkNum)  println(s"Pay with cheque: $checkNum")
    case Card(Visa(), cardNum) => println(s"Pay with a Visa card: $cardNum")
    case Card(MasterCard, cardNum) => println(s"Pay with a MasterCard: $cardNum")
  }

  // example 3
  sealed trait Notification

  case class Email(to: String) extends Notification

  case object Slack extends Notification

  def sendNotification(e: Option[Notification]): Unit = e match {
    case Some(Email(to)) => println(1) // send email
    case Some(Slack) => println(2) // send message to Slack
    case None => println(3)
  }

  def main(args: Array[String]): Unit = {
    // example 1
    val c1 = CashCoupon(1, 0.9, status = true)
    val c2 = DiscountCoupon(2, 0.8, status = true)
    val c3 = GiftCoupon(3, 0.7, status = true)
    val c4 = GroupCoupon(4, 0.6, status = true)

    val s1 = StatusNotFetched(CouponStatusBase(c1))
    val s2 = StatusFetched(CouponStatusBase(c2), 97)
    val s3 = StatusUsed(CouponStatusBase(c3), 98)
    val s4 = StatusExpired(CouponStatusBase(c4), 99)

    pay(s1)
    pay(s2)
    pay(s3)
    pay(s4)
    println()

    // example 2
    val e1 = Cash()
    val e2 = Cheque("123456")
    val e3 = Card(Visa(), "54321")
    val e4 = Card(MasterCard, "98765")
    payMe(e1)
    payMe(e2)
    payMe(e3)
    payMe(e4)

    // example 3
    val r1 = Email("chenfh5@qq.com")
    val r2 = Slack
    sendNotification(Option(r1))
    sendNotification(Option(r2))
    sendNotification(Option(null))
  }

}

Summary

ADT在scala的含义,

  1. 将基本类型组合起来,构成一个复合类型
  2. 使用一些sealed,case class,case特性将简易化解构过程

Reference