Scala 是一种函数式编程语言,也就是说每个函数都是一个值。Scala 有很简洁的语法用于定义匿名函数、curry 化函数(curried function)、应用函数、偏函数以及嵌套函数等。函数式编程由数学函数演变得来,包含大量诸如串联与并联、组合与分离、协变与逆变等一系列概念。本文将就如何实现高阶函数进行阐述,其余部份内容不进行深究。 shell
类型参数化(type parameterization)由类型协变性(Type variance)补充构成,类型协变性的机制则是在类型系统中经过指定诸如协变性(convariant)和逆协变性(contravariant)进行约束。子类型的关系给协变带来了问题——在类型参数化中,子类型之间是什么关系?在Scala中,你能够经过class和trait实现参数化。当在class和trait中使用类型参数时,你可使用 + 号实现协变。类型协变(Convriance)容许对其父类的协变位置中(如返回值)进行重载和使用狭义类型。即 编程
Scala 容许经过“+/-”定义类型参数的协变性, 安全
可变(mutable)对象应该保持不变(invariant),为何?在Scala中,要达到Scalable,就要实现相应的转换能力。一个类型参数应该是不变(invariant),不论它是协变的仍是逆协变的。全部Scala可变集合类都是不变的。咱们用一个例子来讲明为何可变对象应该是不变的。咱们用反证法来论证,如今,你可使用collection.immutable.List以及对应的可变collection.mutable.ListBuffer。由于ListBuffer是可变的,它被声明是不变的: 闭包
final class ListBuffer[A] ...{ ... }
注意,若是声明了一个不可变类型(invarint type),你须要去掉-和+标记符号,由于你不能再为ListBuffer指定其它类型。所以下面会发生编译错误: app
scala> val mxs: ListBuffer[String] = ListBuffer("pants") mxs: scala.collection.mutable.ListBuffer[String] = ListBuffer(pants) scala> val everything: ListBuffer[Any] = mxs <console>:6: error: type mismatch; found : scala.collection.mutable.ListBuffer[String] required: scala.collection.mutable.ListBuffer[Any] val everything: ListBuffer[Any] = mxs
尽管String是scala.Any的子类型,Scala不会将mxs指向到everything,为了理解为何,咱们假设ListBuffer是可变的,而且下列代码不会发生任何编译错误: 编程语言
scala> val mxs: ListBuffer[String] = ListBuffer("pants") mxs: scala.collection.mutable.ListBuffer[String] = ListBuffer(pants) scala> val everything: ListBuffer[Any] = mxs scala> everything += 1 res4: everything.type = ListBuffer(1, pants)
你发现到问题了没有?由于everything是Any类型的,你不能存储任何整型值到一个字符型的集合,这简直在等待灾难的发生。为了不这类型问题的发生,把一个可变对象(mutable objects)保持不变(invariant)是最好不过的办法。若是是集合中不可变对象的话会发生什么?置于不可变对象的协变不会发生任何问题。若是你把ListBuffer改成List,你会直接得到一个指向List[Any]的List[String]实例而不发生任何问题。 函数式编程
scala> val xs: List[String] = List("pants") xs: List[String] = List(pants) scala> val everything: List[Any] = xs everything: List[Any] = List(pants)
这样指向安全的缘由是List是不可变的,你能够添加1 到 xs列表中,而且他会返回一个新的Any类型的List: 函数
scala> 1 :: xs res5: List[Any] = List(1, pants)
再说一次,上述方法是正确的,由于con(::)方法老是返回一个新的List,而它的类型取决于List元素的类型。这里惟一能够同时存储一个整形值类型和一个引用值类型的类型是scala.Any。请记住,这是可变/不可变对象协变一项重要属性。 测试
实际上,函数类型和函数值只不过是相应的类及其实例的语法糖衣。函数类型S => T 等价于参数化类型scala.Function1[S, T],这个类型定义在Scala 标准类库中: ui
trait Function1[-P, +R] { ... }
参数超过一个的函数也可相似地定义,通常而言,n-元函数类型:(T1, T2, …, Tn) => T被解释为Functionn[T1, T2, …, Tn, T]。也就是说,函数就是拥有apply 方法的对象。例如,匿名函数“+1”:x: int => x+1,就是以下函数Function1 的实例:
new Function1[int, int] { def apply(x: int): int = x + 1 }
Scala使用减号(-)表示逆协变,加号(+)表示协变。在Function1中,P是逆协变的,R是协变的。在Scala中,函数包含值和类型。例如,Function1表示任何接收一个参数的函数,问题是为何Function1对参数逆协变而对返回类型协变。
在回答问题以前,咱们用反证法进行论证——对参数协变、对返回类型逆协变会发生什么?假若有这样一个协变参数:val addOne: Function1[Any, Int] = { x: Int => x + 1 }
由于Int是scala.Any的子类,协变参数应该容许上面的代码编译。这段代码的漏洞是你可使用任意的类型参数来调用addOne,只要参数是Any的子类。这样作会引发一系列问题,由于该函数只接收Int。Scala做为一门类型安全的(type-safe)语言,不容许你这样作。另一个惟一可能就是你须要将参数类型声明是不可变的(invariant),可是这样会使得Function1不易扩展。建立一个类型安全的函数的惟一可行方案就是逆协变参数类型。你不能使用一个逆协变的返回类型,考虑以下代码:
val asString: Int => Int = { x: Int => (x.toString: Any) }
这段代码是无效的,由于Any是Int的超类,逆协变就是容许你从狭义类型到达广义类型。那下面这个是否正确:
asString(10) + 20
代码最后把20加进一个字符串值,这明显有问题。在处理参数类型和返回类型时,Scala的强类型系统会停止这类错误的发生。要实现一个灵活的、类型安全的Function1特性,惟一可能实现的的方法就是 参数类型的逆协变和返回类型的协变。
除了顾及参数类型和返回值以外,还要考虑类型子类化(subtyping)的边界问题,即 B <: A下界和B >: A上界的约束问题。 因篇幅如今,不做赘言。
考虑以上问题后,下面讲述高阶函数如何实现。
函数做为参数或做为返回值的函数称为 高阶函数。在Scala的immutable.List.的方法中存在大量的高阶函数,咱们看看其中一个map方法
class List[+A] ... { def map[B](f: A => B) : List[B] }
在编程语言中,除了值传递(call-by-value)、引用传递(call-by-value),还包括名传递(call-by-name)和需求传递(call-by-need)。上述代码中,f: A => B这种函数做为参数进行传递的就是名传递。因使用场景不一样,名传递能够是lambda匿名函数和词法闭包的。像map这种高级函数能够经过for-comrehension或者递归形式实现。
def map[A,B](xs:List[A],f:A=>B):List[B] = { xs match{ case List() => Nil case head::tail=>f(head)::map(tail,f) } }
上述map代码中,经过模式匹配和 :: 组合实现一个类型参数传递的高阶函数。其中f表示函数参数化处理操做。固然,你也能够用尾递归实现并进行柯里化(currying)转换。
添加一个测试例子:
@Test def testHighOrderFunction(){ val xs = List(1, 2, 3) // 匿名函数做为参数进行传递 logger.info(s"${xs map ((x: Int) => x + 1)}") // 匿名函数只有一个参数时,略去参数外围 logger.info(s"${xs map (x => x + 1)}") // pointfree-style写法,占位符代替 logger.info(s"${xs map (_ + 1)}") // 只传递函数名称,函数已经实现包装 def addOne(x: Int) = x + 1 logger.info(s"${xs map addOne}") /** * 递归实现的高阶函数 * @param xs List * @param f 函数 */ def map[A, B](xs: List[A], f: A => B): List[B] = { xs match { case List() => Nil case head :: tail => f(head) :: map(tail, f) } } logger.info(s"${map(xs, addOne)}") }