Scala 高阶函数(high-order function)剖析

Scala 是一种函数式编程语言,也就是说每个函数都是一个值。Scala 有很简洁的语法用于定义匿名函数、curry 化函数(curried function)、应用函数、偏函数以及嵌套函数等。函数式编程由数学函数演变得来,包含大量诸如串联与并联、组合与分离、协变与逆变等一系列概念。本文将就如何实现高阶函数进行阐述,其余部份内容不进行深究。 shell

类型参数化协变与逆协变

类型参数化(type parameterization)由类型协变性(Type variance)补充构成,类型协变性的机制则是在类型系统中经过指定诸如协变性(convariant)和逆协变性(contravariant)进行约束。子类型的关系给协变带来了问题——在类型参数化中,子类型之间是什么关系?在Scala中,你能够经过class和trait实现参数化。当在class和trait中使用类型参数时,你可使用 + 号实现协变。类型协变(Convriance)容许对其父类的协变位置中(如返回值)进行重载和使用狭义类型。即 编程

Scala 容许经过“+/-”定义类型参数的协变性, 安全

  1. 用“+”放在类型参数前表示构造子对于该参数是协变的;
  2. “-”则表示逆协变;
  3. 没有任何符号则表示非协变。
协变至关于Java的泛型T,反之则不成立,逆协变不是类型的强制转换(cast)。由于,这里还涉及到可变(variant)与不可变(invariant)的概念。

可变对象应该保持不变

可变(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)}")
}
相关文章
相关标签/搜索