码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-06-30 18:52:09      阅读:161      评论:0      收藏:0      [点我收藏+]

标签:

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

类型参数化协变与逆协变

类型参数化(type parameterization)由类型协变性(Type variance)补充构成,类型协变性的机制则是在类型系统中通过指定诸如协变性(convariant)和逆协变性(contravariant)进行约束。子类型的关系给协变带来了问题——在类型参数化中,子类型之间是什么关系?在Scala中,你可以通过class和trait实现参数化。当在class和trait中使用类型参数时,你可以使用 + 号实现协变(如Maybe)。类型协变(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指定其它类型。因此下面会发生编译错误:

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 标准类库中:

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)}")
}


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

标签:

原文地址:http://my.oschina.net/Barudisshu/blog/472564

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!