我们将定义好的没有任何实例的类型称为虚类型。
虚类型一般作为一个标志而存在,表明我们不会使用该类型的任何实例,它是用来解决设计问题而存在的。
对于定义必须按照某一特定顺序执行的工作流而言,虚类型作用很大。
下面,我们举一个计算工资的例子。工资计算器必须首先执行“扣税前”的减项操作,然后进行扣税,最后计算器会扣除扣税后的其他减项并算出净收入。
// 定义了密封的特质,起到标志的作用
sealed trait PreTaxDeductions
sealed trait PostTaxDeductions
sealed trait Final
defined trait PreTaxDeductions defined trait PostTaxDeductions defined trait Final
// 为了简单起见,此处用Float类型表示金额
case class Employee(
name: String,
annualSalary: Float,
taxRate: Float, // 所有税种税率相同
insurancePremiumsPerPayPeriod: Float,
_401kDeductionRate: Float, // 税前扣除项,美国退休储蓄计划扣款
postTaxDeductions: Float)
defined class Employee
Pay[Step]对象中包含Step参数,它表示了当前执行的步骤
case class Pay[Step](employee: Employee, netPay: Float)
defined class Pay
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
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
CalculatePayroll.main(Array.empty)
For Buck Trends, the gross vs. net pay every 2 weeks is: $3846.15 vs. $2446.10 or 63.6%
为了使得多个流程环节之间表达式更加美观简洁,这里引入“管道”操作符。‘
object Pipeline {
implicit class toPiped[V](value:V) {
def |>[R] (f : V => R) = f(value)
}
}
defined object Pipeline
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
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)
。
// 虚类型
sealed trait NoFuel
sealed trait Fueled
sealed trait NoO2
sealed trait HasO2
defined trait NoFuel defined trait Fueled defined trait NoO2 defined trait HasO2
final case class Rocket[Fuel, O2] ()
defined class Rocket
def createRocket = Rocket[NoFuel, NoO2]()
defined function createRocket
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
val fueledRocket = addFuel(createRocket)
fueledRocket: Rocket[Fueled, NoO2] = Rocket()
val hasFuelO2Rocket = addO2(fueledRocket)
hasFuelO2Rocket: Rocket[Fueled, HasO2] = Rocket()
val launchRocket = launch(hasFuelO2Rocket)
launchRocket: String = "blastoff"
使用管道操作符
import Pipeline._
import Pipeline._
def launchRocketProcess = createRocket |> addFuel |> addO2 |> launch
defined function launchRocketProcess
launchRocketProcess // 小火箭发射成功
res21: String = "blastoff"