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
表示从-2147483648
到2147483647
这样的一组整数集。值都拥有类型,或者说,每个值都表示一组值的一个成员。
2: Int
例子中,2
是Int
集合的一个成员,也即,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]
例子中,定义的类型RequestResponse
是Error
和HttpResponse
的总和构成,一个RequestResponse
类型的值要么是一个 error,要么是一个 HTTP 响应。
密闭特质
在 Scala 中,密闭特质是更符合语言习惯的总和类型表示。
sealed trait AddressType
case object Home extends AddressType
case object Business extends AddressType
例子中,一个AddressType
要么是一个Home
,要么是一个Buiness
,而不能同时是两者。
子类型
如果A
是B
的子集,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
超类型
如果B
是A
的子集,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 => B
,A => 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
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.