CH00-精要

函数

一个函数是一个从“领域”到“代码域”的映射。函数将领域中的每个元素与代码域中的对应元素进行联结。在 Scala 中,领域和代码域都可以表示为type(类型)。

val square: Int => Int = x => x * x
square(2) 	// 4

高阶函数

高阶函数是 接收一个函数作为参数 或 返回一个函数作为结果 的函数。

trait List[A] {
  def filter(f: A => Boolean): List[A]
}

这个例子中,函数filter接收一个类型为A => Boolean的函数作为参数。

组合子

函数组合子是同时接收、返回函数的高阶函数。

type Conf[A] = ConfigReader => A
def string(name:String):Conf[String] = _.readString(name)
def both(left:Conf[A], right:Conf[B]):Conf[(A, B)] = c => (left(c), right(c))

函数both即为一个接收函数并返回函数的高阶函数。

多态函数

多态函数通常拥有一个或多个类型参数。Scala 本身对多态函数没有支持,但是可以通过特质的多态方法来实现。构造多态函数的特质通常拥有apply方法,因此,可以像普通的函数应用语法一样使用:

case object identity {
  def apply[T](value:T):T = value
}
identity(3)		// 3
indentity("3")	// "3"

这样,通过apply方法的便利,就可以实现多态函数。

类型

一个类型一组值运行时描述。比如Int表示从-21474836482147483647这样的一组整数集。值都拥有类型,或者说,每个值都表示一组值的一个成员。

2: Int

例子中,2Int集合的一个成员,也即,2的类型为Int

代数数据类型(ADT)

一个 ADT 是由产品类型自合而成的类型。

乘积类型(product)

乘积类型是由两个或多个类型的笛卡尔积组合构造而成的。

type Point2D = (Int, Int)

例子中,一个二维点是一个数字与另一个数字的积。即每个该类型的值都有一个 x 轴坐标和 y 轴坐标。

样例类

在 Scala 中,样例类是更符合语言习惯的乘积类型表示。

case class Person(name:String, age:Int)

总和类型(sum)

总和类型通过两个或多个类型的不相交形式来定义。

type RequestResponse = Either[Error, HttpResponse]

例子中,定义的类型RequestResponseErrorHttpResponse的总和构成,一个RequestResponse类型的值要么是一个 error,要么是一个 HTTP 响应。

密闭特质

在 Scala 中,密闭特质是更符合语言习惯的总和类型表示。

sealed trait AddressType
case object Home extends AddressType
case object Business extends AddressType

例子中,一个AddressType要么是一个Home,要么是一个Buiness,而不能同时是两者。

子类型

如果AB的子集,A即为B的子类型。在 Scala 中,A必须继承自B。编译器允许在任何需要的地方使用子类型。

sealed trait Shape{
  def width:Int
  def height:Int
}
case class Rectangle(corner:Point2D, width:Int, height:Int) extends Shape

超类型

如果BA的子集,A即为B的超类型,B必须继承自A。编译器支持无论在何处定义子类型,均可以使用超类型。

同样还是上面的例子,Shape即为Rectangle的超类。

Universals

一个通用(universally)量化类型定义了一个“由一些任意类型参数化的类型”的类别。在 Scala 中,类型构造器(比如特质)和方法必须是通用量化类型,尽管方法并不拥有一个类型,它只是类型的一部分。

类型构造器

一个类型构造器是一个通用量化类型,用于构造类型。

sealed trait List[A]
case class Nil[A]() extends List[A]
case class Cons[A](head: A, List[A]) extends List[A]

例子中,List是一个类型构造器,定义了一组List-类似的类型。因此可以认为,List是针对类型A的通用类型量化。

高阶类型

类型级别函数(Type-Level)

类型构造器可以看做一个类型级别函数,它接收类型并返回类型。这种解释对于理解高阶类型很有用。

比如,List是一个接收类型A并返回类型List[A]的类型级别函数。

类别(Kind)

类别可以认为是“类型的类型”。

  • *:类型的类别,所有类型的集合。
  • * => *:类型级别函数的类别(接收一个类型,返回一个类型)。
  • [*, *] => *:接收两个类型并返回一个类型的类型级别函数 的类别。比如类型构造器Either的类别是[*, *] => *,Scala 语法表示成_[_, _]

可以与函数的类型进行对比:A => BA => B => C

高阶类别(Higher-Order Kinds)

就像函数能够是高阶函数一样,类型构造器也可以是高阶的。Scala 中使用下划线编码(encode)高阶类型构造器。trait CollectionModule[Collection[_]]表示CollectionModule的类型构造器需要提供一个* -> *类别的类型构造器,比如List

  • (* => *) => *:类型构造器的类别,它接收一个* => * 类别的类型构造器。比如:trait Functor[F[_]] {...}trait Monad[F[_]] {...}

Existentials

一个“存在量化类型”定义一个基于一些明确但又未知的类型 的类型。“存在判断的类型”用于隐藏一些并非全局相关的类型信息。

trait ListMap[A]{
  type B
  val list: List[B]
  val mapf: B => A
  def run: List[A] = list.map(mapf)
}

例子中,类型ListMap[A]#B是一些明确类型,但是有不能知道其真正类型,它可能是任何类型。

Skolemization

每个“存在判断的类型”都可以被编码为一个通用(universal)类型。这个过程称为Skolemization

case class ListMap[B, A](list:List[B], mapf: B => A)

trait ListMapInspector[A,Z] {
  def apply[B](value: ListMap[B, A]):Z
}

case class AnyListMap[A] {
  def apply[Z](value:ListMapInspector[A, Z]): Z
}

例子中,除了我们直接使用ListMap,还可以使用AnyListMap,仅当能够为B处理任何类型参数时允许我们对ListMap进行检查。

Type Lambdas

函数可以通过使用下划线操作符编程“偏应用型(partially apply)”。比如,zip(a, _)。一个类型匿名函数是偏应用“高阶类别”类型的一种方式,使用较少的类型参数来生成一个新的类型构造器。

类型匿名函数之于类型构造器,相当于匿名函数之于函数。一个是类型、值的表达式,一个声明:

({type λ[α] = Either[String, α]})#λ

例子中,Either通过偏应用一个String作为第一个类型参数。

较多场景中,会使用**类型别名(type aliase)**替代类型匿名函数,比如:type EitherString[A] = Either[String, A]

类别投影器(Projector)

类别投影器是一个常用的 Scala 编译器插件,提供了更易用的语法来创建类型匿名函数。比如,({type λ[α] = Either[String, α]})#λ可以表示为Either[String, ?]这样的语法。另外有更多语法来创建更复杂的类型匿名函数。

https://github.com/non/kind-projector

类型类

一个类型类是对类型和定义在该类上操作的收集。很多类型类会定义一些规则需要实现来遵守。

trait ShowRead[A] {
  def show(v: A): String
  def read(v: String): Either[String, A]
}

object ShowRead {
  def apply[A](implicit v: ShowRead[A]): ShowRead[A] = v
}

例子中,类型类ShowRead[A]定义了通过渲染字符串来显示类型A,并通过读取字符串来读取它,或是生成一个错误消息。

  • Right Identity

    read(show(v)) == Right(v)
    
  • Left Partial Identity

    read(v).map(show(_)).fold(_ => true, _ == v)
    

实例

一个类型类的实例就是通过提供一组类型来定义一个该类型类的实现。一般这些事例会被标记为imolicit,以便编译器能够自动为需要的函数提供这些实现。

implicit val ShowReadString:ShowRead[String] = new ShowRead[String] {
  def show(v:String):String = v
  def read(v:String): Either[String, String] = Right(v)
}

语法

便利的语法,或称为扩展方法,添加给类型以便这些类型类更加易用:

implicit class ShowOps[A:ShowRead](self:A){
  def show:String = ShowRead[A].show(self)
}
implicit class ReadOps(self:String){
  def read[A:ShowRead]:Either[String, A] = ShowRead[A].read(self)
}

函数式模式

Option、Either、Validation

这些类型均用来表示可选性和偏应用性:

seald trait Maybe[A]
final case class Just[A](value:A) extends Maybe[A]
final case class Empty[A]() extends Maybe[A]

sealed trait \/[A, B]
final case class -\/[A, B](value:A) extends \/[A, B]
fianl case class \/-[A, B](value:B) extends \/[A, B]

type Either[A, B] = A \/ B

sealed trait Validation[A, B]
final case class Failure[A, B](value:A) extends Validation[A, B]
final case class Success[A, B](value:B) extends Validation[A, B]

半群、幺半群(Semigroup, Monoid)

半群支持将两个相同类型的东西组合成另一个新的相同类型的东西。比如,加法操作构成一个基于整数的半群。幺半群怎加一个额外的拥有“零”元素的属性,比如添加给一个值而不改变该值。

trait Semigroup[A]

未完。。。

https://wiki.scala-lang.org/display/SW/Parser+Combinators–Getting+Started