Scala 函数式编程

1、模式匹配

(1)match、case

// 演示 - 模式匹配

def dec(no : Int): Unit = {
  println("#" * 20 + s" e.g. #${no} "  + "#" * 20)
}

// #1 模式匹配小例子
dec(1)
var metric = 0
val mood = ":)"
mood match {
  case ":`(" => println(s"Tearing, I'm sad ${mood}. ") ; metric -= 1
  case ":)" => println(s"Smiling, I'm happy ${mood}"); metric += 1
  case _ => println("Boring, nothing special. "); metric += 0
}
metric


// #2 Case守卫,匹配某种类型的所有值
dec(2)
val inputData = '3'
var digit = -1
inputData match {
  case 'a' => "a"
  case 'b' => "b"
  case 6 => "6"
  case _ if Character.isDigit(inputData) => digit = Character.digit(inputData, 10)
}
s"e.g. #2 Result: digit = ${digit}"
// 关于Character.isDigit的更多示例:
Character.isDigit('a')
Character.isDigit(8)
Character.isDigit('8')



(2)变量和类型

// #3 模式匹配中变量的使用
dec(3)
val someStr = 'A' to 'Z'
someStr(10) match {
    case 1 => "This is 1."
    case '+' => "+ is +"
    case xxx => s"xxx is ${xxx}"    // 这里的变量xxx用于接收someStr(10)\
  //  case _ => "This is _"  // xxx 接收了所有情况,因此不会到这一行,可以去掉
}


// #4 类型匹配
dec(4)
val value: Any = "hello Spark"
// Note: 进行类型匹配时,定义value时,要为Any类型,否则会报错
value match {
  case i: Int  => s"${i} is Int."
  case s: String => s"${s} is String. "
  case c: Char => s"${c} is Char. "
  case _ => "_ is other type. "
}



(3)数组和列表

// #5 匹配数组
dec(5)
val a = Array(1, 2, 3)
a match {
    case Array(0) => "一个元素的Array"
    case Array(x, y) => "2个元素的Array,和为:" + x+y
    case Array(x,y,z) => "3个元素的Array, 乘积为:"  + x * y * z
    case Array(x, _*) => "很多值的Array"
}


// #6 匹配List
dec(6)
val l = List(1,2,3,4,5,6)
l match {
  case List(0) => "一个元素的List"
  case List(x,y) => "2个元素的List,和为:" + x+y
  case List(x,y,z) => "3个元素的List, 乘积为:" + x * y * z
  case List(x,_*) =>"很多值的List"
}




2、样例类

(1)Case Class

  • 在 Class 前面加上 case 这个普通类就变成了样例类
  • 样例类可以进行模式匹配
// 演示 样例类 case class

def dec(no : Int): Unit = {
  println("#" * 20 + s" e.g. #${no} "  + "#" * 20)
}

// #0 常规的类, 不能用于模式匹配
dec(0)
class Grandpa(val occupation: String ) {
  val familyName :String = "Xie"
}
class Father(override val occupation: String) extends Grandpa(occupation)
class Uncle(override val occupation: String) extends Grandpa(occupation)

val father = new Father("Lawyer")
father.occupation
father.familyName
father.isInstanceOf[Grandpa]
//father.isInstanceOf[Uncle]

// #1 case class 样例类, 是可以用于模式匹配的
dec(1)
class School
case class Netease(course:String) extends School
case class SimonXie(course:String) extends School

val test = new SimonXie("666");
// 这里相当于做了一次向上类型转换,用于类型匹配的判断
val bestSchool: School = SimonXie("Spark")
bestSchool match {
  case Netease(course) => s"Netease ${course} is nice. "
  case SimonXie(course) => s"Good to learn ${course} w/ ${bestSchool.getClass.getName}. "
  case _ => "Others"
}



(2)Option 类型

  • Option 类型用样例类来表示那种可能存在也可能不存在的值
  • 样例子类 Some 包装了某个值
  • 样例对象 None 表示没有值
// 演示 Option 类型
// Option类型 用样例类来表示那种可能存在也可能不存在的值

def dec(no:Int): Unit = println("#" * 20 + s" e.g. #${no} " + "#" * 20)

/**
 * Option e.g. #1
 * Map 的get方法他是返回的一个Option:
 * - 如果给定的值没有就返回None;
 * - 如果有值,就会被包含在Some里;
 */
dec(1)
val value = Map("k1" -> 1, "k2" -> 2)
value.get("k3")
value.get("k2") match {
  case Some(v) => s"Has value ${v}. "
  case None => "Has no this value. "
}
value.get("k3") match {
  case Some(v) => s"Has value ${v}. "
  case None => "Has no this value. "
}

// #2 Map.getOrElse
dec(2)
value.getOrElse("v3", 0)
value
/**
 * e.g. #3
 */
dec(3)
val nov3 = value.+("k4"->4)
nov3.get("k3") // Option[Int] = None
for (x <- nov3.get("k3")) {
  println(s"do nothing if x is None. x = ${x}")
}
for (x <- nov3.get("k4")) {
  println(s"get value ${x}")
}
println("nov3-k2:"+ nov3.-("k2"))
for (x <- nov3.-("k2").get("k2")) {
  println(s"do nothing if x is None. x = ${x}")
}
nov3




3、匿名函数

(1)值函数

  在 scala 中,函数就和数值一样,可以在变量中存放函数,并作为参数传递给另一个函数。将一个函数复制给变量语法如下:

// val 变量 = 函数名 _
val v = func _

  示例程序:

// 演示 - 值函数

def dec(no:Int): Unit = println("#" * 20 + s" e.g. #${no} " + "#" * 20)

// #1 将函数作为值赋值给变量
dec(1)
def func1(a : Int): Int = a
val vf1 = func1 _
vf1(1)
vf1(2)

// #2
dec(2)
import scala.math.ceil
val value = 3.14
def func2(a : Double) : Double = {
  ceil(a) + 1.0
}
val vf2 = func2 _
vf2(value)


// #3
dec(3)
val vf3 = ceil _
vf3(value)

// #4
dec(4)
val func3: (Double) => Double = {
  ceil
}
val vf4 = func3
vf4(value)
func3(value)

// #5
dec(5)
/**
 * e.g. #5
 * 这里演示的是如何将函数传递给另外一个函数
 * map是直接接受的函数名作为参数
 * 将数组中所有的元素分别应用到了传入的函数上
 */
Array(1.23, 2.34, 3.45).map(ceil)
Array(1.23, 2.34, 3.45).map(vf2)




(2)匿名函数

  scala 中,无需给每个函数命名,这种函数叫匿名函数,可以将其存在变量中,也可以将其作为参数传递给另一个函数。

// 演示 匿名函数

def dec(no:Int): Unit = println("#" * 20 + s" e.g. #${no} " + "#" * 20)
val dec1 = (no:Int) => println("#" * 20 + s" e.g. #${no} " + "#" * 20)  // 匿名函数

// #1 简单的匿名函数例子
//dec (1)
dec1(10)
(x : Double) => {
  x * 2
}
res1(3)
// 我们可以将匿名函数赋值给变量f
val f  = (x : Double) => x * 2
// 这样变量f包含了这个匿名函数,并可以当做函数来使用
f(4)

// #2 map 方法调用匿名函数,传入的参数是一个函数 function
dec(2)
/**
 * e.g. #2 map方法 调用 匿名函数
 * 无需命名, 就可以将匿名函数传递给另外一个函数
 */
Array(1.23, 2.34, 3.45).map((x:Double) =>{
  x * 2
})
Array(1.23, 2.34, 3.45).map(f)
// [中置表示法],没有句点,这样的写法也比较常见:
Array(1.23, 2.34, 3.45)   map(f)




4、带函数参数的函数

  可以定义一些函数,它们可以接受函数作为参数。

// 常规函数的定义
def regularFunc(param1: Int):Int = {
  param1
}

// 带函数参数的函数
// inputFunc: 传入参数类型、返回值类型均为Double的函数
def actionFunc(inputFunc: (Double) => Double, 
               param: Double) = {
  inputFunc(param)
}

  示例程序:

// 演示 - 带函数参数的函数

import scala.math.sqrt
val dec = (no:Int) => println("#" * 20 + s" e.g. #${no} " + "#" * 20)  // 匿名函数
// 常规函数的定义
def regular(param : Int): Int = {
  param
}

// #1
dec(1)
/**
 * 定义一个带函数参数的函数
 * @param inputFunc  函数的名字,类型是 (Double) => Double
 *                   - 表示输入、输出类型均为 Doube 的函数
 * @return
 */
def actionFunc(inputFunc : (Double) => Double) : Double = {
  inputFunc(9)
}
sqrt(9)
actionFunc(sqrt)

// #2
dec(2)
/**
 * 定义一个常规函数,接收2个参数
 * @param id      整型
 * @param name    字符串型
 * @return        字符串型
 */
def sayHi(id : Int, name : String): String = {
  s"My ID: ${id}, my name: ${name}"
}
def anotherFunc(inputFunc : (Int, String) => String,
                id : Int,
                name : String) : String = {
  inputFunc(id, name)
}
// test
anotherFunc(sayHi, 1, "Simon")



5、闭包

  • 闭包是函数的嵌套
  • 内层函数可以访问外层函数中定义的变量
  • 通常内层函数会使用匿名函数
  • 外层函数的变量可以被 “保护” 起来
// 演示 - 闭包

// 定义了一个闭包函数,其返回值为函数体内的匿名函数
def enclosure(param : Int) : (Int) => Int = {
  (x : Int) => x + param      // 内层的匿名函数使用了外层函数的参数param
}
// 传入10的意义是param1 = 10
val f = enclosure(10)
// 此时f 为函数  (x :Int) =>  x + 10
// 传入闭包的param1 = 10 被 "保护"起来
f(3)   // 此时的3 是匿名函数所需的x, 即x =3, 因此得到结果13
f(6)
f(88)




6、return 表达式

  • 一般情况下,scala 无需显式使用 return 返回函数体的值,函数的最后一行就是返回值;
  • return 用于有名函数里嵌套的内侧匿名函数,将返回值传递给外层的有名函数;
  • Scala 里的匿名函数的 return 背后实际是通过特定的异常来实现的。
/**
 * 结束匿名函数执行,并且在外层也结束并返回内层传递出来的返回值
 * 演示 - return表达式
 */
object DemoReturn {
  def main(args: Array[String]): Unit = {
    val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)

    // #1
    dec(1)
    val testStr = "abcdefg"
    /**
     * 第一个例子, 有名函数 嵌套  匿名函数
     *  -  功能: 判断某个字符ch是否字符串str里
     * @param str String
     * @param ch Char
     * @return 布尔型
     */
    def chInStr(str : String, ch : Char) : Boolean = {
      str.foreach(each => {
        if (each == ch)
          // 有名函数嵌套的匿名函数,可以利用return, 将返回值传递给外层的有名函数
          return true
      })
      false   // 默认返回值false
    }
    println(s"c in ${testStr}: " + chInStr(testStr, 'c'))
    println(s"z in ${testStr}: " + chInStr(testStr, 'z'))

    // #2
    dec(2)
    /**
     * 第2个例子, 有名函数嵌套while循环
     *  - 功能: 返回ch在str中的下标位置
     *
     * @param str 字符串
     * @param ch  字符
     * @return   下标位置Int
     */
    def indexOf(str:String, ch:Char) : Int = {
      var i = 0
      while (i != str.length) {
        if (str(i) == ch)
          return i
        // 有名函数嵌套的while循环块内,可以利用return将返回值传递给外层有名函数
        i += 1
      }
      -1     // 默认值-1,表示未找到
    }
    println(s"testStr=${testStr}, ch=d, index=${indexOf(testStr, 'd')}")
    println(s"testStr=${testStr}, ch=x, index=${indexOf(testStr, 'x')}")

    // #3
    dec(3)
    val list = List("A", "B", "C", "D")
    // 补充例子, 匿名函数的return表达式是特殊的异常
    // 这里没有将含有return表达式的匿名函数,嵌套在有名函数里
    list.foreach(s => {
      if (s == "C") {
        println(s"Got ${s}, ok, do something.")
        return
      }
      println(s"got ${s} , below return statement")
    })
    println("Last Line")
    // 可以看到Last Line没有被打印,因为:
    // 实质上,Scala里匿名函数的return表达式是特殊的异常
  }
}




7、偏应用函数和偏函数

(1)偏函数

  • 偏函数是一个单参数函数,且并未对该类型的所有值都实现相应处理逻辑
  • 偏函数的字面量语法由包围在花括号中的一个或多个 case 语句构成
  • 如果偏函数被应用,而函数的输入却与所有 case 语句不匹配,系统就会抛出一个 MatchError 的运行时错误。可以使用 isDefineAt 方法测试特定输入是否与偏函数匹配,这样偏函数就可以避免抛出 MatchError 错误。
// 演示 - 偏函数 partial function
val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)

// #1
dec(1)
/**
 * 构建一个偏函数,需要实现2个方法:
 * 1. isDefinedAt
 * 2. apply
 *
 * 利用 构建的偏函数pf,实现整型元素的过滤
 *
 * Note: 关于List的collect方法,可以参见scala-docs:
 * Builds a new collection by applying a partial function
 * to all elements of this list
 * on which the function is defined.
 */
val pf = new PartialFunction[Any, Int] {
  def isDefinedAt(any: Any) : Boolean = {
    if (any.isInstanceOf[Int]) true
    else false
  }

  def apply(any: Any) : Int = {
    any.asInstanceOf[Int]
  }
}
List(1,3,5,"test", 7)  collect pf
pf.isDefinedAt("err")
pf.isDefinedAt(9)

// #2
dec(2)

/**
 * 定义一个偏函数,实现对考试成绩的过滤,返回通过的成绩的结果
  */
val examPassed : PartialFunction[Int, String] = {
  case x if x >= 60   => s"Exam passed, score is ${x}"
}
val examFailed: PartialFunction[Int, String]  = {
  case x if x < 60 => s"Exam failed, score is ${x}"
}
val scorePf: PartialFunction[String, Unit] = {
  case x : String => println(x)
}

/*
 * 如果偏函数被调用,而函数的输入却与所有case语句不匹配,系统
 * 就会抛出一个MatchError的运行时错误
 *
 * 55~59的数据不满足case
 */
//(55 to 65 ) map examPassed foreach println
examFailed.isDefinedAt(55)
examPassed.isDefinedAt(100)
examFailed.isDefinedAt(60)
examPassed.isDefinedAt(60)
examPassed(60)
//examPassed(59) // 这里会报出异常scala.MatchError

// 多个偏函数连接在一起执行
(examPassed orElse examFailed)(30)
(examPassed andThen scorePf)(90)



(2)偏应用函数

  偏应用函数是一个表达式,包含函数的部分而非全部参数列表。返回值是一个新函数,此函数携带剩下的参数列表。为了避免潜在的歧义,Scala 要求在后面加上下划线,用来告诉编译器使用目的。

import java.util.Date

/**
 * 演示 - 偏应用函数 partial application
 */
object DemoPartialApp {
  /**
   * 定义一个通用的打印日志函数
   *
   * @param time 接收的时间
   * @param data 传入的需要记录的log消息
   */
  def logger(time: Date, data: String ): Unit = {
    println(s"[${time}]: ${data}")
  }

  def main(args: Array[String]): Unit = {
    val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)

    /**
     * 定义一个休眠的匿名函数
     */
    val nap = (x: Int) => { Thread.sleep(x * 1000)}

    // #1
    dec(1)
    val time = new Date
    logger(time, "line1")
    nap(1)  // 休眠1s
    logger(time, "line2")
    nap(1)
    logger(time, "line3")

    // #2
    dec(2)
    /**
     * e.g. #2
     *
     * 使用偏应用函数,优化以上的功能
     * - 绑定第一个time参数
     * - 第二个参数使用下划线 _ 来替代缺失的参数列表
     */
    val fixedTime = new Date
    val fixTimeLogger = logger(fixedTime, _:String)
    fixTimeLogger("line 4")
    nap(1)
    fixTimeLogger("line 5")
    nap(1)
    fixTimeLogger("line 6")
  }
}




8、柯里化

  柯里化指将原来接收2个参数的函数变成接收1个参数的函数;新的函数返回1个以原有第2个参数作为参数的函数。

// 演示 - 柯里化

val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)

// #1
dec(1)
// 定义一个常规的匿名函数add
// 接收2个参数x和y
val add = (x:Int, y:Int) => {x+y}
add(3, 6)

// #2
dec(2)
val addKry = (x:Int) => {
  y:Int => {x+y}
}
addKry(3)     // 将代表内部匿名函数  (y:Int ) => 3 + y
addKry(3)(6)

// #3
dec(3)
// Scala支持如下的简写来定义柯里化函数:
def addKurry(x:Int)(y:Int) = x + y
addKurry(3)(6)

// #4
dec(4)
/**
 * Array的corresponds方法,是柯里化的一个典型应用
 * NOTE: 在scala-docs:
 * def corresponds[B](that: GenSeq[B])(p: (T, B) ⇒ Boolean): Boolean
 * Tests whether every element of this mutable indexed sequence
 * relates to the corresponding element of another sequence by satisfying a test predicate.
 */
val a = Array("Hello","Scalla")
val b = Array("hello","scalla")
b.corresponds(a) ( (x, y) =>{
  x.equalsIgnoreCase(y)
})
a.corresponds(b)(_.equalsIgnoreCase(_))



9、高阶函数

// 演示 - 一些高阶函数

// map函数, 将一个函数应用到某个集合的所有元素,并返回结果
(1 to 9).map( (x:Int) => 2 * x )
// 简写形式:
val mapRes = 1 to 9 map (2 * _)
println(s"map函数返回值: ${mapRes}")


/**
 * foreach和map函数很类似,只不过它不返回任何结果
 * 只是简单地讲一个函数,分别应用到集合里的每一个元素上
 */
val foreachRes = 1 to 9 foreach println
println(s"foreach函数返回值:${foreachRes}")


/**
 * find 可以找到【第一个】符合条件的元素,而不是返回所有匹配项
 */
1 to 9 find(_>3)
1 to 8 find(_<0)


/**
 * filter 可以用于过滤【所有】符合条件的匹配项
 */
1 to 9 filter(_>3)


/**
 * zip 函数
 */
List(1, 2, 3, 4) zip List(5, 6, 7, 7)


/**
 * flatten 可以将嵌套数据集进行 “展平”
 */
List(
  List(1,2),
  List(3,4)
).flatten


/**
 * flatMap 相当于结合了map和flatten的功能:
 */
List(
  List(1,2),
  List(3,4)
).flatMap( _.map(_*2))

你可能感兴趣的:(Scala 函数式编程)