Scala学习之函数和闭包

前言:当程序变得庞大时,需要将其分割成更小、更易管理的片段。为了控制分割流,我们将代码分
隔成函数。这类似于Java的低内聚、高耦合的要求一样。除了作为对象成员函数的方法之外,还有
内嵌在函数中的函数、函数字面量和函数值。
本章内容:(重要&&有趣)
1、函数的表现形式
2、占位符语法和部分应用函数
3、闭包
4、重复参数
5、控制抽象

函数的表现形式

1、方法

定义:定义函数最通用的方法就是作为某个对象的成员。这种函数被称为方法。

Object LongLines{
  def processFile(fileName: String,width: Int){
    val source = Source.fromFile(fileName)
    for(line<-source.getLines) processLine(filename,width,line)
  }
  private def processLine(filename:String,width:Int,line:String){
    if(line.length>width){
      println("filename+":"+line.trim)
    }
  }
}

2、本地函数

上面的processFile方法展示了函数式编程风格的重要设计原则:程序应该被分割理解成若干个小的函数,每一块都实现一个完备的任务,每一块都很小。这利于让我们去组合更为复杂的事物。但是,这种风格有一个问题,所有这些帮助函数(即每个小块)的名称可能会污染程序的命名空间。Java中的private在Scala中一样有效,但Scala还提供了另一种方式:你可以把函数定义在别的函数之内,就像本地变量那样,这种本地变量只在它的代码块中可见。

def processFile(fileName: String,width: Int){  
  def processLine(filename:String,width:Int,line:String){
    if(line.length>width){
      println(filename+":"+line.trim)
    }
  }
  val source = Source.fromFile(fileName)
  for(line<-source.getLines) processLine(filename,width,line)
}

本地函数可以直接访问包含其函数的参数:

def processFile(fileName: String,width: Int){  
  def processLine(line:String){
    if(line.length>width){
      println(filename+":"+line.trim)
    }
  }
  val source = Source.fromFile(fileName)
  for(line<-source.getLines) processLine(filename,width,line)
}

3、头等函数

Scala的函数是头等函数。我们不仅可以定义和调用函数,还可以把他们写成匿名的字面量并作为值传递。函数字面量被编译进类,并在运行期实例化为函数。因此函数字面量和值的区别在于函数字面量存在于编译期,值出现于运行期。
函数字面量的一些例子:

(x:Int) => x+1
var increase = (x:Int) => x+1
increase = (x:Int) => x+999
increase = (x:Int) => {
  println("wang")
  println("zha")
  println("bangbangda")
  x+1
}

所有的集合类型都可以用foreach方法来遍历,foreach方法以函数作为入参,并对每个元素调用该函数:

var someNumbers = List(1,5,2,88,3)
someNumbers.foreach((x:Int)=>println(x))
//函数字面量的短格式
someNumbers.foreach(x=>println(x))

占位符语法和部分应用函数

占位符语法可以替代部分参数。

someNumbers.foreach(_=>println)


val f = (_:Int)+(_:Int)

部分应用函数可以替代整个参数列表。

def sum(a:Int,b:Int,c:Int) = a+b+c
val a = sum _
a(1,2,3)

上面这个代码的流程是:名为a的变量指向一个函数值对象。这个函数值是由Scala编译器依照部分应用函数表达式sum _,自动产生的一个实例。注意下划线前面要有一个空格,防止把sum_当成一个方法。

sum(1, _:Int,6)

我们再看看上述的一行代码,这是另一种用途,在例子中,提供了第一个和第三个参数,中间的参数缺失。因为这个参数缺失,编译器会产生一个新的函数类,其apply方法带一个参数。在使用一个参数调用时,这个新产生的函数的apply方法调用sum,传入1、6,传递给函数的参数。

如果你正在写一个省略所有参数的偏程序表达式(即部分应用函数表达式),如println _sum _,而且在代码的那个地方正需要一个函数,你可以去掉下划线从而更加简明地表达。

someNumbers.foreach(println _)
someNumbers.foreach(println)

注意只有在需要写函数的地方才可以省略下划线。比如foreach入参是函数,所以println _可以省略成println。而val a = sum _却不能写成val a = sum

闭包

任何以函数字面量为模版创建的函数对象为闭包,前提,该函数字面量中包含自由变量,即闭包的产生过程中,闭包需要动态绑定这个自由变量。

val addMore = (x:Int)=>x+more

addMore:闭包
more:自由变量

资料里叙述了很多,实质上说的就是,自由变量和当次传入的值进行动态绑定。看下面代码:

def makeIncr(more:Int) = (x:Int)=>x+more
val incr1 = makeIncr(1)
val incr2 = makeIncr(9999)
incr1(10) // 11
incr2(10) //10009

重复参数

Scala中,我们可以指定函数的最后一个参数是重复的。满足我们传入可变长度参数列表。想要标注一个重复参数,可在参数类型后面放一个星号:

def echo(args:String*) = foreach(println)

重复参数的类型声明实质上是一个数组。因此,上述echo函数里被声明的其实是一个Array[String]。我们也可以通过下面的这种方式传入数组:

def echo(arr: _*) = foreach(println)
val arr = Array("what's","up",",man")
echo(arr)

_*表示把arr的每个元素当成参数传入,而不是单一的元素传给echo

控制抽象

1、减少代码重复

所有的函数都可以被划分为通用部分和非通用部分。通用部分是函数体,非通用部分是入参。当我们把函数值作为参数时,非通用部分就代表着不同的算法。在这种函数每一次调用中,我们都可以把不同的函数作为入参传入,被调用的函数每次选用参数的时候调用传入的参数值。这种高阶函数让我们有机会去简化代码。
下面我们来看一个例子,以加深理解函数值(函数字面量)用来简化代码:
常规代码:

object FileMatcher{
  private def filesHere = (new java.io.File(".")).listFiles
  def fileEncoding(query:String) = {
    for(file<-filesHere; if (file.getName.endsWith(query))) yield file
  }

  def fileEncoding(query:String) = {
    for(file<-filesHere; if (file.getName.contain(query))) yield file
  }

  def fileEncoding(query:String) = {
    for(file<-filesHere; if (file.getName.matches(query))) yield file
  }
}

简化代码第一步(利用函数字面量):

object FileMatcher{
  private def filesHere = (new java.io.File(".")).listFiles
  def filesMatching(query:String,matcher:(String,String)=>Boolean) = {
    for(file<-filesHere;if(matcher(file.getName,query))) yield file
  }

  def filesEnding(query:String) = filesMatching(query, _.endsWith(_))
  def filesContaining(query:String) = filesMatching(query, _.contain(_)) 
  def filesRegex(query:String) = filesMatching(query, _.matches(_))  
}

简化第二步(利用闭包):

object FileMatcher{
  private def filesHere = (new java.io.File(".")).listFiles
  def filesMatching(matcher:String=>Boolean) = {
    for(file<-filesHere;if(matcher(file.getName))) yield file
  }

  def filesEnding(query:String) = filesMatching( _.endsWith(query))
  def filesContaining(query:String) = filesMatching( _.contain(query)) 
  def filesRegex(query:String) = filesMatching( _.matches(query))  
}

2、简化客户代码

举个例子:我们判断一个传入的值是否被包含在集合中
通常实现代码:

def contain(nums:List[String]):Boolean={
  var exists =false
  for(n<-nums;if(n<0)) exists =true 
}

但是我们可以直接调用Listexists方法:nums.exists(_<0)。exists方法代表了控制抽象。再举一个例子,如果让我们再写一个是否集合中是否含有奇数或者偶数,我们一定也会选择函数值为入参的高阶函数:

def containsOdd(nums:List[Int]) = nums.exists(_%2 == 0)
def containsNeg(nums:List[Int]) = nums.exists(_%2 != 0)

def exists(compare:Int=>Boolean){
  for(n<-nums;if(compare(n))) true
}

3、柯里化的函数式编程技巧

未被柯里化的代码:
这里是一个完整的拥有2个入参的函数。

def plainOldSum(x:Int,y:Int) = x+y
plainOldSum(1,3)  //4

柯里化的代码:
这里是发生了两次函数调用,第一个函数调用了带单个的名为xInt参数,并返回第二个函数的函数值。第二个函数带Int参数y。调用过程等价于def first(x:Int) = (y:Int) => x+y

def plainOldSum(x:Int)(y:Int) = x+y
plainOldSum(1)(3)  //4

简单地说第一步,plainOldSum(1)返回了一个匿名函数,(y:Int) = 1+y,第二步,plainOldSum(3),最终结果为4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值