十、 函数式编程高级

一、偏函数(partial function)

1、基本介绍

  • 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择

  • 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于你打算怎样处理)

  • 偏函数在Scala中是一个特质PartialFunction

2、实例

一个集合val list = List(1, 2, 3, 4, "abc") ,请完成如下要求:

将集合list中的所有数字+1,并返回一个新的集合

要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)

object FunctionTest {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4, "abc")
    //说明
    val addOne3= new PartialFunction[Any, Int] {
      def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
      def apply(any: Any) = any.asInstanceOf[Int] + 1
    }
    val list3 = list.collect(addOne3)
    println("list3=" + list3) //?
  }
}
  • 使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)

  • PartialFunction 是个特质(看源码)

  • 构建偏函数时,参数形式   [Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数

  • 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行isDefinedAt()如果为true ,就会执行 apply, 构建一个新的Int 对象返回

  • 执行isDefinedAt() 为false 就过滤掉这个元素,即不构建新的Int对象.

  • map函数不支持偏函数,因为map底层的机制就是所有循环遍历,无法过滤处理原来集合的元素

  • collect函数支持偏函数

3、偏函数简化形式

声明偏函数,需要重写特质中的方法,有的时候会略显麻烦,而Scala其实提供了简单的方法

object FunctionTest {
  def main(args: Array[String]): Unit = {
    def f2: PartialFunction[Any, Int] = {
      case i: Int => i + 1 // case语句可以自动转换为偏函数
    }
    //    简化形式一
    val list2 = List(1, 2, 3, 4,"ABC").collect(f2)
    println(list2)
    //    简化形式二
    val list3 = List(1, 2, 3, 4,"ABC").collect{ case i: Int => i + 1 }
    println(list3)
  }
}

二、作为参数的函数

函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:function1,即:(参数类型) => 返回类型

object FunctionTest {
  def main(args: Array[String]): Unit = {
    //说明:
    def plus_1(x: Int) = 3 + x
    def plus_2(x:Int):Int ={3 + x}
    //说明
    val result1 = Array(1, 2, 3, 4).map(plus_1)
    val result2 = Array(1, 2, 3, 4).map(plus_1(_))
    println(result1.mkString(","))
  }
}
  • map(plus(_)) 中的plus(_) 就是将plus这个函数当做一个参数传给了map,_这里代表从集合中遍历出来的一个元素。

  • plus(_) 这里也可以写成 plus 表示对 Array(1,2,3,4) 遍历,将每次遍历的元素传给plus的 x

  • 进行 3 + x 运算后,返回新的Int ,并加入到新的集合 result1中

  • def map[B, That](f: A => B) 的声明中的 f: A => B 一个函数

三、匿名函数

没有名字的函数就是匿名函数,可以通过函数表达式 来设置匿名函数

    val triple = (x: Double) => 3 * x
    println(triple(3))
  • (x: Double) => 3 * x 就是匿名函数

  • (x: Double) 是形参列表=> 是规定语法表示后面是函数体 3 * x 就是函数体,如果有多行,可以 {} 换行写.

  • triple 是指向匿名函数的变量。

四、高阶函数

能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。可使应用程序更加健壮。

1、基本使用

object FunctionTest {
  def main(args: Array[String]): Unit = {
      val res = test(sum, 6.0)
      println("res = " + res)
    }
    //test 就是一个高阶函数,它可以接收f: Int=> Double
    def test(f: Int => Double, n1: Double) = {
      f(n1.toInt)
    }
    //sum 是接收一个 Int,返回一个Double
    def sum(d: Int): Double = {
      d + d
    }
}

2、高阶函数可以返回函数类型

  def main(args: Array[String]): Unit = {
    val result = minusxy(3)(5)
    println(s"result = $result")
//    分步执行
    val f1 = minusxy(3)
    val result2 = f1(5)
    println(s"result2 = $result2")
  }
  def minusxy(x: Int) = {
    (y: Int) => x - y
  }
}

(1)说明:  def minusxy(x: Int) = (y: Int) => x - y

函数名为 minusxy

该函数返回一个匿名函数:(y: Int) = > x -y

(2)说明 :  val result3 = minusxy(3)(5)

minusxy(3)执行minusxy(x: Int)得到 (y: Int) => 3 - y 这个匿名函

minusxy(3)(5)执行 (y: Int) => x - y 这个匿名函数

也可以分步执行: val f1 = minusxy(3);   val res = f1(90)

五、参数(类型)推断

参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如  list=(1,2,3) list.map()  map中函数参数类型是可以推断的),同时也可以进行相应的简写

1、参数类型推断写法说明

  • 参数类型是可以推断时,可以省略参数类型

  • 当传入的函数,只有单个参数时,可以省去括号

  • 如果变量只在 => 右边只出现一次,可以用 _来代替

val list = List(1, 2, 3, 4)
println(list.map((x:Int)=>x + 1)) //(2,3,4,5)
println(list.map((x)=>x + 1))
println(list.map(x=>x + 1))
println(list.map(_ + 1))
val res = list.reduce(_+_)

2、上述例子小结

  • map是一个高阶函数,因此也可以直接传入一个匿名函数,完成map

  • 当遍历list时,参数类型是可以推断出来的,可以省略数据类型 Int println(list.map((x)=>x + 1))

  • 当传入的函数,只有单个参数时,可以省去括号 println(list.map(x=>x + 1))

  • 如果变量只在=>右边只出现一次,可以用_来代替 println(list.map(_ + 1))

六、闭包(closure)

1、基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。

//1.用等价理解方式改写 2.对象属性理解
def minusxy(x: Int) = (y: Int) => x - y
//f函数就是闭包.
val f = minusxy(20) 
println("f(1)=" + f(1)) // 19
println("f(2)=" + f(2)) // 18

2、小结

  • (y: Int) => x – y返回的是一个匿名函数 ,因为该函数引用到到函数外的 x,那么  该函数和x整体形成一个闭包,如:这里 val f = minusxy(20) 的f函数就是闭包

  • 你可以这样理解,返回函数是一个对象,而x就是该对象的一个字段,他们共同形成一个闭包

  • 当多次调用f时(可以理解多次调用闭包),发现使用的是同一个x, 所以x不变。

  • 在使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为他们会组合成一个整体(实体),形成一个闭包


3、体会闭包的好处

    def makeSuffix(suffix:String)=(filename:String)=>{
      if(filename.endsWith(suffix)) filename
      else filename+suffix
    }
    val res1 = makeSuffix(".jpg")("test")
    val res2 = makeSuffix(".jpg")("test.jpg")
    println(s"res1 = $res1 , res2 = $res2")
  • 返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量 组合成一个闭包,因为 返回的函数引用到suffix这个变量

  • 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把!

七、函数柯里化(curry)

1、基本介绍

  • 函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化

  • 柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作。

  • 不用设立柯里化存在的意义这样的命题。柯里化就是以函数为主体这种思想发展的必然产生的结果。(即:柯里化是面向函数思想的必然产生结果)

2、实例

object FunctionTest {
  def main(args: Array[String]): Unit = {
//    编写一个函数,接收两个整数,可以返回两个数的乘积,要求:
    val n1 = 10
    val n2 = 20
    println(mul1(n1,n2))
    println(mul2(n1)(n2))
    println(mul3(n1)(n2))
  }
//  使用常规的方式完成
  def  mul1(n1:Int,n2 :Int):Int={
    n1 * n2
  }
//  使用闭包的方式完成
  def mul2(n1:Int)=(n2:Int)=>{
    n1 * n2
  }
//  使用函数柯里化完成
  def mul3(n1:Int)(n2:Int) = n1 * n2
}

3、比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
全部转大写(或小写)

比较是否相等

针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)

方式一、

//方式1: 简单的方式,使用一个函数完成.
def eq2(s1: String)(s2: String): Boolean = {
    s1.toLowerCase == s2.toLowerCase
}

方式2:使用稍微高级的用法(隐式类)

object FunctionTest {
  def main(args: Array[String]): Unit = {
//    比较两个字符串在忽略大小写的情况下是否相等,
    val s1 = "a"
    val s2 = "b"
    //
    val b2 = s1.checkEq(s2)(eq)
    println(b2)
//下面三种写法等价,并且可以表示一个演变过程
    s1.checkEq(s2)((s1:String,s2: String) =>s1.equals(s2))
    s1.checkEq(s2)((s1,s2) =>s1.equals(s2))
    s1.checkEq(s2)(_.equals(_))
  }
  //接收两个字符串,比较是否相等
  def eq(s1: String, s2: String): Boolean = {
    s1.equals(s2)
  }
//  隐式类,
//  TestEq(s: String),字符串可以调用该隐式类中的方法
  implicit class TestEq(s: String) {
//  接收一个String值,
//  将比较字符串分散成两步执行
//  checkEq(ss: String)完成将字符串完成大小写转换任务
//  f 接收两个字符串,完成比较字符串的任务
    def checkEq(ss: String)(f: (String, String) => Boolean): Boolean = {
      f(s.toLowerCase, ss.toLowerCase)
    }
  }
}

八、控制抽象

1、控制抽象基本介绍

(1)控制抽象是这样的函数,满足如下条件

  • 参数是函数

  • 函数参数没有输入值也没有返回值

    def myRunInThread(f1: () => Unit) = {
      new Thread {
        override def run(): Unit = {
          f1()
        }
      }.start()
    }
    myRunInThread {
      () => println("干活咯!5秒完成...")
        Thread.sleep(5000)
        println("干完咯!")
    }

(2)上例简化处理,省略(),如下形式

    def myRunInThread(f1:  => Unit): Unit = {//说明 
      new Thread {
        override def run(): Unit = {
          f1
        }
      }.start()
    }
    myRunInThread {
       println("干活咯!5秒完成...")
        Thread.sleep(5000)
        println("干完咯!")
    }

2、进阶用法:实现类似while的until函数

var x = 10
def until(condition: => Boolean)(block: => Unit): Unit = {
  //类似while循环,递归
  if (!condition) {
    block
    until(condition)(block)
  }
}
until(x == 0) {
  x -= 1
  println("x=" + x)
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值