1 虚类型

我们将定义好的没有任何实例的类型称为虚类型。

虚类型一般作为一个标志而存在,表明我们不会使用该类型的任何实例,它是用来解决设计问题而存在的。

对于定义必须按照某一特定顺序执行的工作流而言,虚类型作用很大。

下面,我们举一个计算工资的例子。工资计算器必须首先执行“扣税前”的减项操作,然后进行扣税,最后计算器会扣除扣税后的其他减项并算出净收入。


In [1]:
// 定义了密封的特质,起到标志的作用
sealed trait PreTaxDeductions
sealed trait PostTaxDeductions
sealed trait Final


defined trait PreTaxDeductions
defined trait PostTaxDeductions
defined trait Final

In [2]:
// 为了简单起见,此处用Float类型表示金额
case class Employee(
    name: String,
    annualSalary: Float,
    taxRate: Float, // 所有税种税率相同
    insurancePremiumsPerPayPeriod: Float,
    _401kDeductionRate: Float, // 税前扣除项,美国退休储蓄计划扣款
    postTaxDeductions: Float)


defined class Employee

Pay[Step]对象中包含Step参数,它表示了当前执行的步骤


In [3]:
case class Pay[Step](employee: Employee, netPay: Float)


defined class Pay

In [4]:
object Payroll {
    // 每两周发一次薪水,为了简单,认定每年正好52周
    
    // 调用Payroll类的每一个方法均需要传入Pay[Step]对象
    def start(employee: Employee): Pay[PreTaxDeductions] =
        Pay[PreTaxDeductions](employee, employee.annualSalary / 26.0F)
    
    def minusInsurance(pay: Pay[PreTaxDeductions]): Pay[PreTaxDeductions] = {
        val newNet = pay.netPay - pay.employee.insurancePremiumsPerPayPeriod
        pay copy (netPay = newNet)
    }
    
    def minus401k(pay: Pay[PreTaxDeductions]): Pay[PreTaxDeductions] = {
        val newNet = pay.netPay - (pay.employee._401kDeductionRate * pay.netPay)
        pay copy (netPay = newNet)
    }
    
    def minusTax(pay: Pay[PreTaxDeductions]): Pay[PostTaxDeductions] = {
        val newNet = pay.netPay - (pay.employee.taxRate * pay.netPay)
        pay copy (netPay = newNet)
    }
    
    def minusFinalDeductions(pay: Pay[PostTaxDeductions]): Pay[Final] = {
        val newNet = pay.netPay - pay.employee.postTaxDeductions
        pay copy (netPay = newNet)
    }
}


defined object Payroll

In [5]:
object CalculatePayroll {
    def main(args: Array[String]) = {
        val e = Employee("Buck Trends", 100000.0F, 0.25F, 200F, 0.10F, 0.05F)
        val pay1 = Payroll start e
        // 401K和保险扣除的顺序可以交换
        val pay2 = Payroll minus401k pay1
        val pay3 = Payroll minusInsurance pay2
        val pay4 = Payroll minusTax pay3
        val pay = Payroll minusFinalDeductions pay4
        val twoWeekGross = e.annualSalary / 26.0F
        val twoWeekNet = pay.netPay
        val percent = (twoWeekNet / twoWeekGross) * 100
        println(s"For ${e.name}, the gross vs. net pay every 2 weeks is:")
        println(
        f" $$${twoWeekGross}%.2f vs. $$${twoWeekNet}%.2f or ${percent}%.1f%%")
    }
}


defined object CalculatePayroll

In [6]:
CalculatePayroll.main(Array.empty)


For Buck Trends, the gross vs. net pay every 2 weeks is:
 $3846.15 vs. $2446.10 or 63.6%

2 引入管道操作符

为了使得多个流程环节之间表达式更加美观简洁,这里引入“管道”操作符。‘


In [7]:
object Pipeline {
    implicit class toPiped[V](value:V) {
        def |>[R] (f : V => R) = f(value)
    }
}


defined object Pipeline

In [8]:
object CalculatePayroll2 {
    def main(args: Array[String]) = {
        import Pipeline._
        import Payroll._
        val e = Employee("Buck Trends", 100000.0F, 0.25F, 200F, 0.10F, 0.05F)
        val pay = start(e) |>
        minus401k |>
        minusInsurance |>
        minusTax |>
        minusFinalDeductions
        val twoWeekGross = e.annualSalary / 26.0F
        val twoWeekNet = pay.netPay
        val percent = (twoWeekNet / twoWeekGross) * 100
        println(s"For ${e.name}, the gross vs. net pay every 2 weeks is:")
        println(
        f" $$${twoWeekGross}%.2f vs. $$${twoWeekNet}%.2f or ${percent}%.1f%%")
    }
}


defined object CalculatePayroll2

In [9]:
CalculatePayroll2.main(Array.empty)


For Buck Trends, the gross vs. net pay every 2 weeks is:
 $3846.15 vs. $2446.10 or 63.6%

管道操作符实际上只是重排了表达式中各个标记的次序。

例如:|>操作符对pay1 |> Payroll.minus401k进行转化,转化后的表达式是Payroll.minus401k(pay1)

3 发射火箭的例子


In [10]:
// 虚类型
sealed trait NoFuel
sealed trait Fueled
sealed trait NoO2
sealed trait HasO2


defined trait NoFuel
defined trait Fueled
defined trait NoO2
defined trait HasO2

In [11]:
final case class Rocket[Fuel, O2] ()


defined class Rocket

In [12]:
def createRocket = Rocket[NoFuel, NoO2]()


defined function createRocket

In [13]:
def addFuel[O2](x: Rocket[NoFuel, O2]) = Rocket[Fueled, O2]()

def addO2[Fuel](x : Rocket[Fuel, NoO2]) = Rocket[Fuel, HasO2]()

def launch(x : Rocket[Fueled, HasO2]) = "blastoff"


defined function addFuel
defined function addO2
defined function launch

In [14]:
val fueledRocket = addFuel(createRocket)


fueledRocket: Rocket[Fueled, NoO2] = Rocket()

In [20]:
val hasFuelO2Rocket = addO2(fueledRocket)


hasFuelO2Rocket: Rocket[Fueled, HasO2] = Rocket()

In [21]:
val launchRocket = launch(hasFuelO2Rocket)


launchRocket: String = "blastoff"

使用管道操作符


In [22]:
import Pipeline._


import Pipeline._

In [23]:
def launchRocketProcess = createRocket |> addFuel |> addO2 |> launch


defined function launchRocketProcess

In [24]:
launchRocketProcess // 小火箭发射成功


res21: String = "blastoff"