文章目录
  1. 1. 入门
  2. 2. Abstract type members
  3. 3. Self type members
  4. 4. Building a scalable component
  5. 5. Building an extensible component
    1. 5.1. THE EXPRESSION PROBLEM AND THE EXTENSIBILITY CHALLENGE
    2. 5.2. practice example
    3. 5.3. SOLVING THE EXPRESSION PROBLEM
  6. 6. Types of types
    1. 6.1. Structural types
    2. 6.2. Higher-kinded types
    3. 6.3. Phantom types
  7. 7. Ad hoc polymorphism with type classes
    1. 7.1. Solving the expression problem using type classes

入门

It provides the following features:

  • Error detection—Think of the compiler as a suite of test cases that can detect common type and other program errors.
  • Abstractions—This is the focus of this chapter. You’ll learn how the type system provides abstractions to build components.
  • Documentation—The signature of a function or method tells you a lot about what it’s doing.
  • Efficiency—The type system helps the compiler generate optimized binary code.

scala 提供的抽象工具

Technique Description
Modular mixin a mechanism for composing traits for designing reusable components without the problems of multiple inheritance.
Abstract type Scala lets you declare abstract type members to class, trait, and subclasses that can provide concrete types.
Self type A minin use fields or methods of the class it’s mixed into

Abstract type members

Abstract types are those whose identity is unknown at the time of declaration. Unlike concrete types, the type of an abstract type member is specified during the concrete implementation of the enclosing class.

1
2
3
trait Calculator { type S }

class SomeCalculator extends Calculator { type S = String }

The benefit of abstract type members is they can hide the internal information of a component.

You’re going to build a price calculator that can take a product ID and return the price of the product. There can be many ways to calculate the price, and each way could use a different type of data source to retrieve the price.

1
2
3
4
5
6
7
8
9
10
trait Calculator {
  def initialize: DbConnection
  def close(s: DbConnection): Unit
  def calculate(productId: String): Double = {
    val s = initialize
    val price = calculate(s, productId)
    close(s)
    price
  }
  def calculate(s: DbConnection, productId: String): Double

The problem with the current implementation is that it’s hard-wired to a DAO, and a calculator that uses a different kind of data source won’t be able to use the calculator.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 模板方法
trait Calculator {
  type S
  def initialize: S
  def close(s: S): Unit
  def calculate(productId: String): Double = {
    val s = initialize
    val price = calculate(s, productId)
    close(s)
    price
  }
  def calculate(s: S, productId: String): Double
}

Self type members

The self type annotation allows you to access members of a mixin trait or class, and the Scala compiler ensures that all the dependencies are correctly wired before you’re allowed to instantiate the class.

1
2
3
4
5
6
7
trait B {
  def b:Unit = ...
}
//  trait A is defining a dependency to trait B
trait A {  self: B =>
  def a:Unit = b
}

Because the required services are annotated with self type this , you can still access those services, and the Scala compiler will ensure that the final class gets mixed with a trait or a class that implements RequiredServices.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
trait Connection {
  def query(q: String): String
}
trait Logger {
  def log(l: String): Unit
}
trait RequiredServices {
  def makeDatabaseConnection: Connection
  def logger: Logger
}

trait ProductFinder { this: RequiredServices =>
  def findProduct(productId: String) = {
    val c = makeDatabaseConnection
    c.query("find the lowest price")
    logger.log("querying database...")
  }
}
// 有点像spring的简化
trait TestServices extends RequiredServices {
  def makeDatabaseConnection =
    new Connection { def query(q: String) = "test" }
  def logger = new Logger { def log(l: String) = println(l) }
}

object FinderSystem extends ProductFinder with TestServices

Building a scalable component

To build a generic product ordering system. It will be reusable in that a user can order any kind of product.

  • An order component that represents the order placed by the customer.
  • An inventory component that stores the products. You need to check the inventory to make sure you have the product before you place the order.
  • A shipping component that knows how to ship an order to customer.
1
2
3
4
5
6
7
trait OrderingSystem {
  type O <: Order
  type I <: Inventory
  type S <: Shipping
  trait Order { def placeOrder (i: I): Unit }
  trait Inventory { def itemExists(order: O): Boolean }
  trait Shipping {def scheduleShipping(order: O): Long }

You still need to implement the steps for placing the order. Here they are:

  • Check whether that item exists in inventory.
  • Place the order against the inventory. (Inventory will reduce the count by the amount of product in the inventory.)
  • Schedule the order for shipping.
  • If the item doesn’t exist in inventory, return without placing the order and possibly notify Inventory to replenish the product.
1
2
3
4
5
6
7
8
9
10
11
// declared in OrderingSystem
// Self type annotation for two mixin traits
trait Ordering { this: I with S =>
  def placeOrder(o: O): Option[Long] = {
    if(itemExists(o)) {
      o.placeOrder (this)
      Some(scheduleShipping(o))
    }
    else None
  }
}

to use it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object BookOrderingSystem extends OrderingSystem {
  type O = BookOrder
  type I = AmazonBookStore
  type S = UPS
  class BookOrder extends Order {
    def placeOrder(i: AmazonBookStore): Unit = ...
  }
  trait AmazonBookStore extends Inventory {
    def itemExists(o: BookOrder) = ...
  }
  trait UPS extends Shipping {
    def scheduleShipping(order: BookOrder): Long = ...
  }
  object BookOrdering extends Ordering with AmazonBookStore with UPS
}

import BookOrderingSystem._
BookOrdering.placeOrder(new BookOrder)

Building an extensible component

THE EXPRESSION PROBLEM AND THE EXTENSIBILITY CHALLENGE

The goal is to define a data type and operations on that data type in which one can add new data types and operations without recompiling existing code, but while retaining static type safety.

Any implementation of the expression problem should satisfy the following requirements:

  • Extensibility in both dimensions. You should be able to add new types and operations that work on all the types. (I look into this in detail in this section.)
  • Strong static type-safety. Type casting and reflection are out of the question.
  • No modification of the existing code and no duplication.
  • Separate compilation.

practice example

You have a payroll system that processes salaries for full-time employees in the United States and Canada:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case class Employee(name: String, id: Long)

trait Payroll {
  def processEmployees(employees: Vector[Employee]): Either[String, Throwable]
}

class USPayroll extends Payroll {
  def processEmployees(employees: Vector[Employee]) = ...
}

class CanadaPayroll extends Payroll {
  def processEmployees(employees: Vector[Employee]) = ...
}

// With current changes in the business, you also have to process salaries of full-time
// employees in Japan. 
class JapanPayroll extends Payroll {
  def processEmployees(employees: Vector[Employee]) = ...
}

This is one type of extension the expression problem talks about. The solution is type-safe, and you can add JapanPayroll as an extension and plug it in to an existing payroll system with a separate compilation.

What happens when you try to add a new operation?

1
2
3
4
5
6
7
8
9
10
11
// In this case the business has decided to hire contractors,
// and you also have to process their monthly pay.
case class Employee(name: String, id: Long)
case class Contractor(name: String)
//  The new Payroll interface should look like the following:
trait Payroll extends super.Payroll {
  def processEmployees(
      employees: Vector[Employee]): Either[String, Throwable]
  def processContractors(
      contractors: Vector[Contractor]): Either[String, Throwable]
}

The problem is you can’t go back and modify the trait because that will force you to rebuild everything—which you can’t do because of the constraint put on you by the expression problem.

Using the Visitor pattern to solve this problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case class USPayroll {
  def accept(v: PayrollVisitor) = v.visit(this)
}
case class CanadaPayroll {
  def accept(v: PayrollVisitor) = v.visit(this)
}
trait PayrollVisitor {
  def visit(payroll: USPayroll): Either[String, Throwable]
  def visit(payroll: CanadaPayroll): Either[String, Throwable]
}
class EmployeePayrollVisitor extends PayrollVisitor {
  def visit(payroll: USPayroll): Either[String, Throwable] = ...
  def visit(payroll: CanadaPayroll): Either[String, Throwable] = ...
}

// you can easily create a new class called ContractorPayrollVisitor
class ContractorPayrollVisitor extends PayrollVisitor {
  def visit(payroll: USPayroll): Either[String, Throwable] = ...
  def visit(payroll: CanadaPayroll): Either[String, Throwable] = ...
}
// Using the Visitor pattern, it’s easy to add new operations,
// but what about type

If you try to add a new type called JapanPayroll, you have a problem. You have to go back and change all the visitors to accept a JapanPayroll type.

SOLVING THE EXPRESSION PROBLEM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
trait PayrollSystem {
  case class Employee(name: String, id: Long)
  type P <: Payroll
  trait Payroll {
    def processEmployees(
      employees: Vector[Employee]): Either[String, Throwable]
  }
  def processPayroll(p: P): Either[String, Throwable]
}

trait USPayrollSystem extends PayrollSystem {
  class USPayroll extends Payroll {
    def processEmployees(employees: Vector[Employee]) =
      Left("US payroll")
  }
}

trait CanadaPayrollSystem extends PayrollSystem {
  class CanadaPayroll extends Payroll {
    def processEmployees(employees: Vector[Employee]) =
      Left("Canada payroll")
  }
}

object USPayrollInstance extends USPayrollSystem {
  type P = USPayroll
  def processPayroll(p: USPayroll) = {
    val employees: Vector[Employee] = ...
    val result = p.processEmployees(employees)
    ...
  }
}

// add new Payroll Type
trait JapanPayrollSystem extends PayrollSystem {
  class JapanPayroll extends Payroll {
    def processEmployees(employees: Vector[Employee]) = ...
  }
}

// Now add a new method to the Payroll trait
// without recompiling everything
trait ContractorPayrollSystem extends PayrollSystem {
  type P <: Payroll
  case class Contractor(name: String)
  trait Payroll extends super.Payroll {
    def processContractors(
        contractors: Vector[Contractor]): Either[String, Throwable]
  }
}

trait USContractorPayrollSystem extends USPayrollSystem with
    ContractorPayrollSystem {
  class USPayroll extends super.USPayroll with Payroll {
    def processContractors(contractors: Vector[Contractor]) =
      Left("US contract payroll")
  }
}

// use it
object RunNewPayroll {
  object USNewPayrollInstance extends USContractorPayrollSystem {
    type P = USPayroll
    def processPayroll(p: USPayroll) = {
      p.processEmployees(Vector(Employee("a", 1)))
      p.processContractors(Vector(Contractor("b")))
      Left("payroll processed successfully")
    }
  }
  def main(args: Array[String]): Unit = run
  
  def run = {
    val usPayroll = new USPayroll
    USNewPayrollInstance.processPayroll(usPayroll)
  }
}

Types of types

Structural types

Structural typing in Scala is the way to describe types by their structure, not by their names, as with other typing.

1
2
3
4
5
6
7
8
9
10
11
12
def close(closable: { def close: Unit }) = {
  closable.close
}

// used like this
type Closable = { def close: Unit }
def close(closable: { def close: Unit }) = {
  closable.close
}

def amountPaidAsSalary2(workers: Vector[{def salary: BigDecimal }]) = {
}

Higher-kinded types

Higher-kinded types are types that know how to create a new type from the type argument.

The scala.collections.immutable.List[+A] is an example of a higher-kinded type. It takes a type parameter and creates a new concrete type. import scala.language.higherKinds to enable it

Phantom types

Phantom types are types that don’t provide any constructors to create values. You only need these types during compile time to enforce constraints.

1
2
3
4
5
6
7
case class Order(itemId: Option[Item], address: Option[String])

def addItem(item: String, o: Order) =
  Order (Some(item), o.shippingAddress)
def addShipping(address: String, o: Order) =
  Order (o.itemId, Some(address))
def placeOrder (o: Order) = { ... }

The problem with this approach is that the methods could get called out of order.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sealed trait ItemProvided
sealed trait NoItem
sealed trait AddressProvided
sealed trait NoAddress

case class Order[A, B, C](itemId: Option[String], shippingAddress: Option[String])

def emptyOrder = Order[InCompleteOrder, NoItem, NoAddress](None, None)

def addItem[A, B](item: String, o: Order[A, NoItem, B]) =
  o.copy[A, ItemProvided, B](itemId = Some(item))

def addShipping[A, B](address: String, o: Order[A, B, NoAddress]) =
  o.copy[A, B, AddressProvided](shippingAddress = Some(address))

def placeOrder (o: Order[InCompleteOrder, ItemProvided, AddressProvided]) ={
  ...
  o.copy[OrderCompleted, ItemProvided, AddressProvided]()
}

Ad hoc polymorphism with type classes

Ad hoc polymorphism is a kind of polymorphism in which polymorphic functions can be applied to arguments of different types. Ad hoc polymorphism lets you add features to a type any time you want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
trait XmlConverter[A] {
  def toXml(a: A): String
}

case class Movie(name: String, year: Int, rating: Double)

object Converters {
  implicit object MovieConverter extends XmlConverter[Movie] {
    def toXml(a: Movie) = <movie>
            <name>{a.name}</name>
            <year>{a.year}</year>
            <rating>{a.rating}</rating>
        </movie>.toString
  }
}

object Main {
  import Converters._
  def toXml[A](a: A)(implicit converter: XmlConverter[A]) =
      converter.toXml(a)
  def main(args: Array[String]) = {
    val p = Movie("Inception", 2010, 10)
    toXml(p)
  }
}

// 可以写成这样, scala2.8 之后的语法糖
def toXml[A: XmlConverter](a: A) =
   implicitly[XmlConverter[A]].toXml(a)

Solving the expression problem using type classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import scala.langage.higherkinds

object PayrollSystemWithTypeclass {
  case class Employee(name: String, id: Long)
  trait PayrollProcessor[C[_], A] {
    def processPayroll(payees: Seq[A]): Either[String, Throwable]
  }
  case class USPayroll[A](payees: Seq[A])
       (implicit processor: PayrollProcessor[USPayroll, A]) {
    def processPayroll = processor.processPayroll(payees)
  }
  case class CanadaPayroll[A](payees: Seq[A])
      (implicit processor: PayrollProcessor[CanadaPayroll, A]){
    def processPayroll = processor.processPayroll(payees)
  }
}

object PayrollProcessors {
  import PayrollSystemWithTypeclass._
  implicit object USPayrollProcessor extends PayrollProcessor[USPayroll, Employee] {
    def processPayroll(payees: Seq[Employee]) = Left("us employees are processed")
  }
  implicit object CanadaPayrollProcessor extends PayrollProcessor[CanadaPayroll, Employee] {
    def processPayroll(payees: Seq[Employee]) =
        Left("canada employees are processed")
  }
}
// 扩展他时

object PayrollSystemWithTypeclassExtension {
  import PayrollSystemWithTypeclass._
  case class JapanPayroll[A](payees: Vector[A])
        (implicit processor: PayrollProcessor[JapanPayroll, A]) {
    def processPayroll = processor.processPayroll(payees)
  }
  case class Contractor(name: String)
}

object PayrollProcessorsExtension {
  import PayrollSystemWithTypeclassExtension._
  import PayrollSystemWithTypeclass._
  implicit object JapanPayrollProcessor extends
        PayrollProcessor[JapanPayroll, Employee] {
    def processPayroll(payees: Seq[Employee]) =
         Left("japan employees are processed")
  }
}

implicit object USContractorPayrollProcessor
      extends PayrollProcessor[USPayroll, Contractor] {
  def processPayroll(payees: Seq[Contractor]) =
        Left("us contractors are processed")
}
// TODO

object RunNewPayroll {
  import PayrollSystemWithTypeclass._
  import PayrollProcessors._
  import PayrollSystemWithTypeclassExtension._
  import PayrollProcessorsExtension._
  def main(args: Array[String]): Unit = run
  def run = {
    val r1 = JapanPayroll(Vector(Employee("a", 1))).processPayroll
    val r2 = JapanPayroll(Vector(Contractor("a"))).processPayroll
  }
}
文章目录
  1. 1. 入门
  2. 2. Abstract type members
  3. 3. Self type members
  4. 4. Building a scalable component
  5. 5. Building an extensible component
    1. 5.1. THE EXPRESSION PROBLEM AND THE EXTENSIBILITY CHALLENGE
    2. 5.2. practice example
    3. 5.3. SOLVING THE EXPRESSION PROBLEM
  6. 6. Types of types
    1. 6.1. Structural types
    2. 6.2. Higher-kinded types
    3. 6.3. Phantom types
  7. 7. Ad hoc polymorphism with type classes
    1. 7.1. Solving the expression problem using type classes