下面,我们举一个计算工资的例子。工资计算器必须首先执行“扣税前”的减项操作,然后进行扣税,最后计算器会扣除扣税后的其他减项并算出净收入。
In [1]:
// 定义了密封的特质,起到标志的作用
sealed trait PreTaxDeductions
sealed trait PostTaxDeductions
sealed trait Final
In [2]:
// 为了简单起见,此处用Float类型表示金额
case class Employee(
name: String,
annualSalary: Float,
taxRate: Float, // 所有税种税率相同
insurancePremiumsPerPayPeriod: Float,
_401kDeductionRate: Float, // 税前扣除项,美国退休储蓄计划扣款
postTaxDeductions: Float)
Pay[Step]对象中包含Step参数,它表示了当前执行的步骤
In [3]:
case class Pay[Step](employee: Employee, netPay: Float)
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)
}
}
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%%")
}
}
In [6]:
CalculatePayroll.main(Array.empty)
In [7]:
object Pipeline {
implicit class toPiped[V](value:V) {
def |>[R] (f : V => R) = f(value)
}
}
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%%")
}
}
In [9]:
CalculatePayroll2.main(Array.empty)
管道操作符实际上只是重排了表达式中各个标记的次序。
例如:|>操作符对pay1 |> Payroll.minus401k进行转化,转化后的表达式是Payroll.minus401k(pay1)。
In [10]:
// 虚类型
sealed trait NoFuel
sealed trait Fueled
sealed trait NoO2
sealed trait HasO2
In [11]:
final case class Rocket[Fuel, O2] ()
In [12]:
def createRocket = Rocket[NoFuel, NoO2]()
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"
In [14]:
val fueledRocket = addFuel(createRocket)
In [20]:
val hasFuelO2Rocket = addO2(fueledRocket)
In [21]:
val launchRocket = launch(hasFuelO2Rocket)
使用管道操作符
In [22]:
import Pipeline._
In [23]:
def launchRocketProcess = createRocket |> addFuel |> addO2 |> launch
In [24]:
launchRocketProcess // 小火箭发射成功