Add函数没有副功用,「函数(Function)」是函数式编程的为主单元

在接纳的哪一刻,成败实已显出端倪。

桃花落,闲池阁,山盟虽在,锦书难托,莫,莫,莫!

Scala语言的表征

Scalable语言
Scala是一门可伸缩的scalable语言,既能够写复杂的劳务器端程序,也足以写简单的台本
正当的面向对象
拥有的定义最终都会被限期为方正的目的
函数式编程的天性
函数式程序思想!!!
无缝的Java互操作
打造于Jvm之上,Java的包可以在Scala中应用,huo1Scala写好的主次给Java调用
编程思路灵活
既可以面向对象的思辨,也得以函数式编程的思考

Scala具有三种参数传递的艺术:Call-by-Value(按值传递)与Call-by-Name(按名传递)。Call-by-Value防止了参数的重新求值,效能相对较高;而Call-by-Name防止了在函数调用时刻的参数求值,而将求值拖延至实际调用点,但有可能引致重复的表达式求值。

「函数(Function)」是函数式编程的为主单元。本文将首要谈论函数式理论中多少个不难模糊的定义,最后通过ScalaHamcrest的实战,加深对那一个概念的知情和运用。

Scala之父:Martin Odersky

导读
函数式变成的定义和沉思
Scala的费用环境搭建
Scala语言的基本功
Scala中的类型和求值策略
Scala中函数的概念
Immutable Collections如何用函数式思想贯彻数据结构和其上的局地操作

二者存在微妙的歧异,并动用于不一致的风貌。本文将演讲两者之间的差别,不分厚薄点探究Call-by-Name的贯彻情势和利用场景。

  1. 函数类型
  2. 函数形态
  3. 柯里化
  4. 高阶函数
  5. 一些使用函数
  6. 偏函数
  7. 实战ScalaHamcrest
  8. 总结

函数式编程思想

  1. 基本概念
  • val与值
  • def与方法
  • val与var
  • val与def
  1. 参数传递
  • 按值传递
  • 按名传递
  1. 筹资方式

函数类型

Scala中,函数可以运用「函数字面值(Function
Literal)」直接定义。有时候,函数字面值也不时称为「函数」,或「函数值」。

(s: String) => s.toLowerCase

只用纯函数编程

定义:函数式编程是一种编程范式,营造统计机程序和布局的办法和作风,把统计当做数学函数求值的历程,并且幸免了改变状态和可变的数额

基本概念

匿名函数

其实,「函数字面值」本质上是一个「匿名函数(Anonymous
Function)」。在Scala里,函数被转移为FunctionN的实例。上例等价于:

new Function1[String, String] {
  def apply(s: String): String = s.toLowerCase
}

其中,Function1[String, String]可以简写为String => String,由此它又等价于:

new (String => String) {
  def apply(s: String): String = s.toLowerCase
}

约等于说,「函数字面值」可以视作「匿名函数对象」的二个「语法糖」。

纯函数特点 Pure Function

纯函数,没有副功用的函数

val与值

val用来「变量申明」与「值(Value)」定义。例如,pi概念了多少个常量,它向来持有Double品类的字面值。

val pi = 3.1415926

val也可以直接定义「函数值(Function
Literals)」。例如,max变量定义了四个品种为(Int, Int) => Int的函数值。

val max = (x: Int, y: Int) => Int = if (x > y) x else y

当使用val概念变量时,其引用的目标将被马上求值。max在概念时,它立刻对=的出手表明式举办求值,它平昔持有(Int, Int) => Int花色的函数值。上例等价于:

val max = new Function2[Int, Int, Int] {
  def apply(x: Int, y: Int): Int = if (x > y) x else y
}

但是,apply办法并从未立即被求值。直至发生函数调用时才会对apply展开求值。

函数值

综上述,函数实际上是FunctionN[A1, A2, ..., An, R]品类的1个实例而已。例如,(s: String) => s.toLowerCaseFunction1[String, String]项目标二个实例。

在函数式编程中,函数做为一等国民,函数值能够被轻易地传递和仓储。例如,它可以赋予一个变量lower

val lower: String => String = _.toLowerCase

比方存在1个map的柯里化的函数。

def map[A, B](a: A)(f: A => B): B = f(a)

函数值可以用作参数传递给map函数。

map("HORANCE") { _.toLowerCase }

不曾副功效:状态的变更

例如:调用 def Add(y:Int) = x + y
其结果为xy之和,并且调用之后没有引起x值的转移,没有副成效
之所以,Add函数没有副效能

def与方法

def用以定义「方法(Method)」。例如,max概念了一个(Int, Int)Int的方法,它表示max是多少个参数类型为(Int, Int),重返值类型为Int的章程定义。

def max(x: Int, y: Int): Int = if (x > y) x else y

当使用def定义方法时,其方法体并从未即时被求值。不过,每当调用两遍max,方法体将被再次地被求值。

盛名函数

相对于「匿名函数」,假如将「函数值」赋予def,或者val,此时函数常称为「闻名函数」(Named
Function)。

val lower: String => String = _.toLowerCase
def lower: String => String = _.toLowerCase

两者之间存在微妙的出入。前者接纳val概念的变量直接持有函数值,数次运用lower将回来同三个函数值;后者使用def概念函数,每一趟调用lower将收获不一致的函数值。

引用透明性

对于上述Add函数,对于同一输入y,重返结果均一致
因而,Add具有引用透明性

归来函数

能够将上例max艺术举行转换,使其重返(Int, Int) => Int的函数值。

def max = (x: Int, y: Int) => if (x > y) x else y 

此时,max概念了一个主意,但简单了参数列表,其再次来到值类型为(Int, Int) => Int。它等于于

def max() = (x: Int, y: Int) => if (x > y) x else y 

因为max是2个「无副成效」的法门,依据常规,能够略去「空参数列表」,即省略max后边的小括号()。一则对外宣示无副效用的语义,二则使代码越发简明。

函数调用

Scala里,「函数调用」实际上等价于在FunctionN实例上调用apply办法。例如,存在多少个有名函数lower

val lower: String => String = _.toLowerCase

当爆发如下函数调用时:

lower("HORANCE")

它等价于在Function1[String, String]品类的实例上调用apply方法。

lower.apply("HORANCE")
怎样确保引用透明

不变性Immutablity:任何的情状和值都以不变的,才能取得引用透明
函数与变量,对象类是同一级的,即函数中得以定义函数,有变量的地点都足以动用函数,都以同等的

主意与函数

def max(x: Int, y: Int): Int = if (x > y) x else y
def max = (x: Int, y: Int) => if (x > y) x else y 

双面都定义为「方法(Method)」,但后者再次回到了二个函数(Function)类型。由此,后者平常也被习惯地称呼「函数(Function)」。

率先,它们两者可以有所相同的调用格局:max(1, 2)。但对此后人,调用进程实际上包罗了八个子进度。

  1. 率先调用max返回(Int, Int) => Int的实例;
  2. 然后再在该函数的实例上调用apply艺术,它等价于:

max.apply(1, 2)

附带,两者获取函数值的主意各异。后者可以直接得到到函数值,而对从前者要求实践η增添才能博得万分的片段使用函数。

val f = max _

此时,f也扭转为(Int, Int) => Int的函数类型了。实施上,对于上例,η增添的历程看似于如下试下。

val f = new (Int, Int) => Int {
  def apply(x: Int, y: Int): Int = max(x, y)
}

形式化

一般地,函数字面值具有如下方式:

(a1: A1, a2: A2, ..., an: An) => E: R

其中,E是1个具有类型为R的表达式。它的档次为:

(A1, A2, ..., An) => R

要么表示为:

FunctionN[A1, A2, ..., An, R]

里头,函数字面值等价于匿名函数对象的定义:

new FunctionN[A1, A2, ..., An, R] {
  def apply(a1: A1, a2: A2, ..., an: An): R = E
}

其中,FunctionN类型定义为

trait FunctionN[-A1, -A2, ..., -An, +R] {
  def apply(a1: A1, a2: A2, ..., an: An): R
}

高阶函数

函数作为2个函数的输入或另壹个函数的输出

val与var

varval都可以用于定义变量,但双边表示不一样的语义。val若果引用了目的,便无法再一次引用其它对象了。

val s1 = "Alice"
s1 = "Bob"   // Error

var引用变量可以每一天变动去引用此外的对象。

var s2 = "Alice"
s2 = "Bob"  // OK

另外,var/val都可以引用不可变(Immutable)类的实例,也得以引用可变(Mutable)类的实例。

val s1 = new StringBuilder  // val可以引用可变类的实例
var s2 = "Alice"            // var也可以引用不可变类的实例

var/val的出入在于引用变量本人的可变性,前者表示援引随时可修改,而后者表示援引不可修改,与它们所引用的靶子是还是不是可变无关。

高阶函数

收受函数作为参数,或再次回到函数的函数平日称为「高阶函数(Hign Order
Function)」。高阶函数是组合式设计的基本功,它极大地提升了代码的可复用性。

例如,starts, ends, contains是多个高阶函数,它们都回到String => Boolean品种的谓词,用于判断有个别字符串的表征。

type StringMatcher = String => String => Boolean

def starts: StringMatcher = prefix =>
  _ startsWith prefix

def ends: StringMatcher = suffix =>
  _ endsWith suffix

def contains: StringMatcher = substr =>
  _ contains substr

同样地,高阶函数也可以接受函数类型作为参数。例如,若是要不经意大小写,并复用上边多少个函数,可以定义ignoringCase的高阶函数,用于修饰既有的字符串匹配器。

def ignoringCase(matcher: StringMatcher): StringMatcher = substr => 
  str => matcher(substr.toLowerCase)(str.toLowerCase)

可以如下格局使用ignoringCase

assertThat("Horance, Liu", ignoringCase(starts)("horance"))

其中,assertThat也是一个高阶函数。

def assertThat[A](actual: A, matcher: A => Boolean) =
  assert(matcher(actual))

闭包 closure

val与def

def用来定义方法,val定义值。对于「重回函数值的不二法门」与「直接动用val概念的函数值」之间存在微妙的出入,即便它们都定义了同等的逻辑。例如:

val max = (x: Int, y: Int) => if (x > y) x else y 
def max = (x: Int, y: Int) => if (x > y) x else y 

有个别使用函数

所谓「部分行使函数」(Partially Applied
Function),即至少还有二个参数还未规定的函数。例如,times用来判断m是不是n的整数倍。

def times: Int => Int => Boolean = n => m => m % n == 0

对此大规模的翻番关系,可以先确定参数n的值。例如:

val twice  = times(2)
val triple = times(3)

此时,twice, triple是一个Int => Boolean的函数,可以被重新行使。

twice(4)    // true
triple(9)   // true

表明式求值

函数式编程中,一切都是表明式,表明式求值策略:

严谨求值:call by value
非严酷求值:call by name

语义差别

虽说两者之间仅设有一字之差,但却存在本质的歧异。

  1. def用来定义「方法」,而val用于定义「值」。
  2. def概念的法门时,方法体并未被当下求值;而val在概念时,其引述的对象就被立刻求值了。
  3. def概念的方法,每回调用方法体就被求值一遍;而val仅在概念变量时仅求值两回。

比如,每一回使用val定义的max,都是行使同一个函数值;也等于说,如下语句为真。

max eq max   // true

而每趟使用def定义的max,都将回到不相同的函数值;也等于说,如下语句为假。

max eq max   // false

其中,eq透过相比较对象id万事亨通相比较对象间的同一性的。

η扩展

第一,将原本的times展开一下更换。

def times(n: Int)(m: Int): Boolean = m % n == 0

此时,若是利用如下的点子确定twice, triple,将生出错误。

val twice  = times(2)  // Error
val triple = times(3)  // Error

因为,times(2)的诚实类型为格局(Method),且有着(Int)Boolean的门类。它不是函数,且尚未函数值。

可以经过在「方法」的前边手动地加上「3个空格和1个_」,将艺术转换为二个「部分采取函数」,那种转移常称为「η扩展」。例如:

val twice  = times(2) _
val triple = times(3) _

事实上,times(2) _将生成五个匿名的函数对象。它等价于:

val twice = new Function1[Int, Boolean] { 
  def apply(m: Int): Boolwan = this.times(2)(m)  // 在this对象调用`times`方法
}

惰性求值

概念表明式时不会马上求值,只在率先次调用时才求值

项目参数

val意味着了一种饿汉求值的构思,而def意味着了一种惰性求值的思辨。然而,def具有更好可伸张性,因为它可以辅助项目参数。

def max[T : Ordering](x: T, y: T): T = Ordering[T].max(x, y)

电动扩张

假定上下文已经申明函数的连串,编译器会自动执行η扩展。例如:

def atom(matcher: Int => Boolean, action: Int => String): Int => String =
  m => if (matcher(m)) action(m) else ""

因为,atommatcher参数已经评释了函数原型。由此,当传递times(3), to("Fizz")atom时,编译器会自动执行η扩展,将times(3), to("Fizz")变动为二个局地选取函数。

val r_n1 = atom(times(3), to("Fizz"))

它等于于

val r_n1 = atom(times(3) _, to("Fizz") _)

其中,to的贯彻为:

def to(s: String): Int => String = _ => s

递归函数

函数式编程中绝非循环语句,整体的循环用递归完成
调优递归:尾递归

lazy惰性

def在定义方法时并不会爆发实例,但在每一遍方法调用时生成差别的实例;而val在概念变量时便生成实例,未来每便使用val概念的变量时,都将赢得同2个实例。

lazy的语义介于defval之间。首先,lazy valval语义类似,用于定义「值(value)」,包罗函数值。

lazy val max = (x: Int, y: Int) => if (x > y) x else y 

其次,它又不无def的语义,它不会在概念max时就落成求值。但是,它与def不等,它会在第四回采纳max时形成值的概念,对于尔后再也行使max将重临相同的函数值。

偏函数

JSONObject是一个JSON的壹个子类,它富有bindings: Map[String, JSON]toString迭代地拍卖Map中元组,并透过调用_1, _2方法分别收获当前迭代元组的紧要性字和值。

case class JSONObject(bindings: Map[String, JSON]) extends JSON {
  override def toString: String = {
    val contents = bindings map { t =>
      "\\\\"" + t._1 + ":" + t._2.toString + "\\\\""
    }
    "{" + (contents mkString ",") + "}"
  }
}

可以拔取「格局匹配」直接拿走Map中当前迭代的元组。

bindings map { {
  case (key, value) => "\\\\"" + key + ":" + value + "\\\\""
} }

其中,map的大括号(或小括号)平常被略去。

bindings map {
  case (key, value) => "\\\\"" + key + ":" + value + "\\\\""
}

其中,{ case (key, value) => ??? }表明式是一个完好无缺,蕴涵外面的大括号。其余,该表达式是有档次的,它的类型为PartialFunction[(String, JSON), String]

val f: PartialFunction[(String, JSON), String] = {
  case (key, value) => "\\\\"" + key + ":" + value + "\\\\""
}

也等于说,{ case (key, value) => ??? }是「偏函数」(Partial
Function)的1个字面值。

函数式编程的独到之处

Lisp是第一种函数式编程语言

  1. 编程代码量少
  2. 当协会完含数之后,对于同样输入,输出相同,便于调试
  3. 分外适用于并行编程,没有副成效,具备引用透明性,在n个节点运算结果是一律的
  4. 古板语言多核编程非凡复杂

参数传递

Scala留存二种参数传递的不二法门。

  • Pass-by-Value:按值传递
  • Pass-by-Name:按名传递

偏函数的定义

相似地,对于函数A => B,它对A花色的所有值都有意义;而PartialFunction[A, B]仅对A品类的片段值有意义。

约等于说,偏函数是1个欠缺的函数,它只处理格局匹配成功的值,而对情势匹配失利的值抛出MatchError异常。例如,positive就是三个偏函数,它仅对超过0的整数有意义。

val positive: PartialFunction[Int, Int] = { case x if x > 0 => x }

当传递负数或0值时,将在运转时抛出MatchError异常。

positive(1)   // OK
positive(-1)  // MatchError

Scala环境的搭建

安装Jdk6以上,并安装Scala包

按值传递

默许情形下,Scala的参数是依照值传递的。

def and(x: Boolean, y: Boolean) = x && y

对此如下调用语句:

and(false, s.contains("horance"))

表达式s.contains("horance")先是会被随即求值,然后才会传送给参数y;而在and函数体内再一次利用y时,将不会再对s.contains("horance")表达式求值,直接得到开首伊始被求值的结果。

分析偏函数

偏函数是一个一元函数。

trait PartialFunction[-A, +B] extends (A => B) {
  def isDefinedAt(x: A): Boolean
  def applyOrElse[A1 <: A, B1 >: B](x: A1)(default: A1 => B1): B1 =
    if (isDefinedAt(x)) apply(x) else default(x)
}

对此偏函数positive

val positive: PartialFunction[Int, Int] = { case x if x > 0 => x }

为了方便清楚,上述偏函数已毕类似于如下的匿名函数定义:

val positive = new PartialFunction[Int, Int] { 
  def isDefinedAt(x: A): Boolean = x > 0
  def apply(x: Int): Int = x   
}

Scala基础语法

传递函数

将上例and兑现修改一下,让其颇具函数类型的参数。

def and(x: () => Boolean, y: () => Boolean) = x() && y()

其中,() => Boolean等价于Function0[Boolean],表示参数列表为空,再次来到值为Boolean的函数类型。

调用方法时,传递参数必须显式地抬高() =>的函数头。

and(() => false, () => s.contains("horance"))

此时,它约等于于如下落成:

and(new Function0[Boolean] { 
  def apply(): Boolean = false
}, new Function0[Boolean] {
  def apply(): Boolean = s.contains("horance")
}

此时,and艺术将如约「按值传递」将Function0的七个对象引用分别传递给了xy的引用变量。但时,此时它们函数体,例如s.contains("horance"),在参数传递以前并没有被求值;直至在and的艺术体内,xy调用了apply艺术时才被求值。

约等于说,and措施能够等价落成为:

def and(x: () => Boolean, y: () => Boolean) = x.apply() && y.apply()

追溯MatchError

当调用positive(-1)时,将抛出MatchError非凡。追本溯源,它事实上调用的是AbstractPartialFunctionapply方法。

AbstractPartialFunction的存在,避免了偏函数的字面值爆发大量的匿名类定义。

abstract class AbstractPartialFunction[-T, +R] extends PartialFunction[T, R] {
  def apply(x: T): R = applyOrElse(x, PartialFunction.empty)
}

其中,empty定义在PartialFunction的伴生对象中。

object PartialFunction {
  val empty = new PartialFunction[Any, Nothing] {
    def isDefinedAt(x: Any) = false
    def apply(x: Any) = throw new MatchError(x)
    ...
  }
}

当调用positive(-1)时,AbstractPartialFunctionapply方法被调用,它委托给了PartialFunctionapplyOrElse方法。

因为那时传到的是负数,isDefinedAt返回false,由此最终偏函数的再次来到值为PartialFunction.empty,而调用empty.apply将抛出MatchError异常。

变量修饰符

  1. val 定义 immutable variable 常量
  2. var 定义 mutable variable 变量
  3. lazy val 惰性求值的常量

概念时绝不彰显的证实项目,scala会本身进行变量推导

前二种概念,在概念时表明式就会及时求值
lazy val
在REPL中,scala会给没有变量名的变量自动取值resN,能够一直引用已有个别resN
注意:
scala中不允许常量定义后被直接改动,而变量var可以
val x = 10
x = 20 //会报错reassignment to val
val x = 20 //正确的再一次赋值,必要利用val重新定义
对于lazy val,注意没有lazy var,一般是定义惰性求值的表达式
val l = 常量或变量组成的表明式

按名传递

通过Function0[R]的参数类型,在传递参数前落实了延期开端化的技术。但落实中,参数传递时必须构造() => R的函数值,并在调用点上显式地抬高()完成apply办法的调用,存在不少的语法噪声。

因此,Scala提供了别的一种参数传递的建制:按名传递。按名传递略去了富有()语法噪声。例如,函数已毕中,xy不要显式地抬高()便得以成功调用。

def and(x: => Boolean, y: => Boolean) = x && y

说不上,调用点用户无需构造() => R的函数值,但它却拥有延迟开端化的功能。

and(false, s.contains("horance"))

提升

一旦对偏函数举办进步(lift),会将偏函数升级为三个常见的一元函数。例如,对于偏函数positive:

val positive: PartialFunction[Int, Int] = { case x if x > 0 => x } 

通过lift,可以将positive提升为Int => Option[Int]的一元函数。

val positiveOption = positive.lift

positiveOption盛传的是正数x时,将返回Some(x),否则重返None

Scala的类连串

Any 所有类的父类
AnyVal 值类型
NumericTypes 数值类型 Byte,Shot,Int,Long,Float,Double
Boolean 布尔档次
Char 字符类型
Unit 空类型,相当于Java的void
AnyRef 所有引用类型的父类
All java.* ref types 所有Java的引用类都以其子类
All scala.* ref types 所有自定义的scala的类都以其子类
Null 所有引用类型的最后2个子类
Nothing 所有连串的末尾二个子类(既是AnyVal又是AnyRef的子类)

  1. NumericTypes
    对此数值类型:低精度能够赋值给高精度,反之不行,数据会缺失:报类型不匹配错误
  2. Unit
    几度作为函数的重返值出现,申明函数有副功能
  3. Null
    意味着壹个引用类型的值为空。平常不选用
  4. Nothing
    对于函数而言,如果重返值为nothing,那么则意味函数极度
    scala> def foo() = throw new Exception(“1111”)
    foo: ()Nothing
  5. String
    新性子 – 字符串插值(interpolation)
    scala> val name=”Jack”
    name: String = Jack
    scala> s”my name is $name” //使用字符串插值
    res11: String = my name is Jack

借款形式

能源回收是总计机工程举办中一项主要的兑现形式。对于所有GC的先后设计语言,它只是完成了内存财富的自行回收,而对于诸如文件IO,数据库连接,Socket连天等能源必要程序员自行达成财富的回收。

该难题能够方式化地描述为:给定1个财富R,并将能源传递给用户空间,并回调算法f: R => T;当进度停止时财富自动释放。

  • Input: Given resource: R
  • Output:T
  • Algorithm:Call back to user namespace: f: R => T, and make
    sure resource be closed on done.

之所以,该兑现形式也平日被号称「借贷格局」,是确保财富自动回收的第一机制。本文通过using的指雁为羹控制,透视Scala在那些领域的规划技术,以便巩固「按名传递」技术的应用。

降级

一旦对一元函数positiveOption调用Function.unlift,将使得该一元函数降级为八个偏函数。例如:

val positive = Function.unlift(positiveOption)   // positive: PartialFunction[Int, Int]

其中,unlift落实在单键对象Function中,它的函数原型为:

def unlift[T, R](f: T => Option[R]): PartialFunction[T, R] = ???

代码块Block

代码块用于集体七个表明式:{exp1;exp2}
五个表明式在一行2时须要分号分割,代码块本事也是一个表明式
说到底的求值,是终极2个表达式

支配抽象:using

import scala.language.reflectiveCalls

object using {
  type Closeable = { def close(): Unit }

  def apply[T <: Closeable, R](resource: => T)(f: T => R): R = {
    var source = null.asInstanceOf[T]
    try {
      source = resource
      f(source)
    } finally {
      if (source != null) source.close
    }
  }
}

函数形态

Scala中,大概存在三种作风看似的函数定义(广义的函数定义)。首先,能够以艺术(Method)的款式存在。

def lower(s: String): String = s.toLowerCase

非凡地,即便以艺术的花样存在,但它回到了函数类型。由此,也时不时成为艺术。

def lower: String => String = _.toLowerCase
def lower: Function1[String, String] = _.toLowerCase

别的,也得以采取val具备函数值。

val lower: String => String = _.toLowerCase
val lower: Function1[String, String] = _.toLowerCase
val lower = (s: String) => s.toLowerCase

若果存在一个map的柯里化的函数。

def map[A, B](a: A)(f: A => B): B = f(a)

它承受上述6种风格的lower定义。

map("HORANCE")(lower)

当然,map也得以直接收受匿名函数(函数字面值)。

map("HORANCE") { _.toUpperCase }

虽然map接受两种差其他lower意味着,但二种象征格局存在微妙的差别。

函数

概念函数的法子:
def functionName(param:paramType):returnType = {
//body expressions
}
示例:

    object worksheetA {
        // 完整形式
        def Hello(name:String):String = {
                s"Hello,$name"
        }                                 //> Hello: (name: String)String
        Hello("Jack")                     //> res0: String = Hello,Jack
        // 省略返回值,自动推断类型
        def Hello2(name:String) = {
                s"Hello,$name"
        }                                 //> Hello2: (name: String)String
        Hello2("Tom")                     //> res1: String = Hello,Tom
        // 单语句的函数体
        def add(x:Int,y:Int) = {
                x + y
        }                                 //> add: (x: Int, y: Int)Int
        // 可以省略大扩号
        //def add(x:Int,y:Int) = x + y
        add(1,2)                          //> res2: Int = 3
    }

客户端

比如如下程序,它读取用户根目录下的README.md文本,并传递给usingusing会将文件句柄回调给用户空间,用户达成公文的逐行读取;当读取完结后,using活动关闭文件句柄,释放能源,但用户无需关注那几个细节。

import scala.io.Source
import scala.util.Properties

def read: String = using(Source.fromFile(readme)) { 
  _.getLines.mkString(Properties.lineSeparator)
}

主意与函数

def lower(s: String): String = s.toLowerCase
def lower: String => String = _.toLowerCase

从严刻意义上讲,两者都是应用了def概念的「方法」(Method)。然则,后者重临了一个「函数类型」,并保有函数调用的语义。

从而根据习惯,前者常称为「方法」,而后人则常常称为「函数」;前者代表了面向对象的风格,后者代表了函数式的风格。

Scala中,方法与函数是多个不一样的概念,并存在微妙的距离,并表示了二种区其余编程范式。但有幸的是,它们的差距性对于99%的用户是无感知的。

scala中的if

if是表明式,而不是话语
if(逻辑表达式) valA else valB
val a = 1 //> a : Int = 1
if(a!=1) “not none” //> res3: Any = ()返回空
if(a!=1) “not none” else a //> res4: Any = 1

鸭子编程

type Closeable = { def close(): Unit }概念了贰个Closeable的品体系名,使得T非得是负有close措施的子类型,那是Scala支撑「鸭子编程」的一种重大技术。例如,File满足T类型的表征,它有着close方法。

艺术不是函数

Scala中,方法并不是函数。例如

def lower(s: String): String = s.toLowerCase

lower是2个措施(Method),而艺术是从未有过值的,由此它不可以给予给变量。

val f = lower   // 错误,lower是一个方法,它没有值

可以对章程lower实施η扩充,将其更换为部分应用函数。例如:

val f = lower _  // f: String => String = <function1>

事实上,lower _将生成二个匿名的函数对象。它等价于:

val f = new Function1[String, String] { 
  def apply(s: String): String = this.lower(s)  // 在this对象调用`lower`方法
}

良好地,当上下文要求三个「函数类型」时,此时编译器能够自动完成「方法」的η扩展。例如,map的第一个参数刚好是一个函数类型。此时,能够一向将lower措施开展传递,编译器自动已毕η恢宏,将其更换为一个函数对象。

map("HORANCE")(lower)   // map期待一个函数类型,编译器自动完成`η`扩展

它等价于:

map("HORANCE")(lower _)

scala中的for comprehension

用来落实循环的一种推导式,本身是由map() reduce()组合落成的
是scala语法糖(thin text sugar)的一种

        for{
            x <- xs
            y = x + 1
            if( y > 0)
        }yield y

示例:

    object worksheetA {
        //初始化一个List
        val list = List("alice","bob","cathy")
        for (
                //遍历list每一个元素给s,generator
                s <- list
        )println(s)
        for {
                s <- list
                //串长度大于三才被打印
                if( s.length > 3)
        }println(s)
        val res_for = for{
                s <- list
                //变量绑定,variable binding
                s1 = s.toUpperCase()
            if ( s1 != "")
        //yeild导出的意思,如果每次s1不空,则生成新的collection
        }yield (s1)
    }

惰性求值

resource: => T是按照by-name传送,在实参传递形参进度中,并未对实参举行当下求值,而将求值推延至resource: => T的调用点。

对此本例,using(Source.fromFile(source))语句中,Source.fromFile(source)并没有立即发生调用并传递给形参,而将求值贻误至source = resource语句。

两等级调用

def lower: String => String = _.toLowerCase

事实上,此处的lower是一个运用def概念的不二法门。不过非常地,它回到了函数类型。当发生如下调用时。

lower("HORANCE")

实在,它做了两件事情:

  • this对象上调用lower方法,并返回String => String花色的函数对象;
  • 在该函数对象上调用apply方法;

相当于说,上例函数调用等价于:

val f = this.lower  // f: String => String = <function1>
f.apply("HORANCE")

事实上,lower("HORANCE")的调用具有非凡意义。其一,lowerthis对象上调用刚好回到函数类型;其二,FunctionN上的apply是1个异样的措施,其有着更合适的函数调用语义。

按照习惯,即便lower是多个艺术定义,但也被平常称为函数定义。

scala中的try

try也是一个表明式,重临二个值

    try{
        Integer.praseInt("dog")
    }catch{
        case _ => 0  //下划线是通配符,统配所有异常
    }finally{
        print("总是会打印");
    }

def与val

一般来说三种方法,定义了看似的名牌函数,但两者之间存在微妙的差别。

val lower: String => String = _.toLowerCase
def lower: String => String = _.toLowerCase

scala中的macth

类似switch,但也是二个表达式,重返相应的值,首要用在 pattern match

    var expression = 1                  //> expression  : Int = 1
    expression match{
        case 1 => "dog"
        case 2 => "cat"
        //类似switch的default
        case _ => "others"
    }                                   //> res5: String = dog

拔取val定义函数

前者采纳val的变量直接持有函数值,多次拔取lower将回来同三个函数值。也等于说,如下表明式求值为真。

lower eq lower   // true

Scala的求值策略

scala中存有运算都是基于表达式的,求值会有不一样政策

  1. call by value

对函数实参求值,仅求几回,求得的值间接沟通函数中的形式参数

  1. call by value

不会对函数实参举办表达式求值,直接把表明式传入函数体内,替换表明式的形参,然后在函数内每一趟使用到此形参时会被求值

接纳def定义函数

后人使用def概念函数,每一遍调用lower将获取不一致的函数值。相当于说,如下表明式求值为假。

lower eq lower   // false

scala平日接纳call by value

def foo(x: Int) = x //call by Value
def foo(x: => Int) = x //call by Name

下边是三种求值策略在差距境况下的运营机制:

def add(x: Int,y: Int) = x * x      def add(x: =>Int,y: =>Int) = x * x
add(3+4,7)                          add(3+4,7) 
=>add(7,7)                          =>(3+4)*(3+4)
=>7*7                               =>7*(3+4)
=>49                                =>7*7
                                    =>49

add(7,3+4)                          add(7,3+4)
=>add(7,7)                          =>7*7
=>7*7                               =>49
=>49                     

只顾上述运营机制的区分

scala> def bar(x:Int, y: => Int) : Int = 1
bar: (x: Int, y: => Int)Int

scala> def loop():Int = loop
loop: ()Int

scala> bar(1,loop) 
//loop函数位于的参数的定义方式是y: => Int,即call by name,不进行求值,会带到函数体内并且使用时
才求值,此处,loop没有机会执行。
res0: Int = 1

scala> bar(loop,1)
//loop函数位于的参数的定义方式是y: Int,即call by value,会直接将表达式求值并代替形参,此处loop
首先被执行求值,故而陷入死循环。
输出:死循环

举行函数设计和调用时,三种差别要搞领会

柯里化

「柯里化(Currying)」是函数式理论的三个第一概念。例如,以前的map就是二个柯里化的法门定义。

def map[A, B](a: A)(f: A => B): B = f(a)

柯里化类型的自行推演,及其控制结构的肤浅等利用场景扮演关键角色。

Scala中的函数

  1. 支撑把函数作为实参传递给其它3个函数
  2. 支撑把函数作为重返值
  3. 支撑把函数赋值给变量
  4. 辅助把函数存储在数据结构里

即,在scala中,函数跟日常变量一样选用,且独具函数的连带项目

类型推演

假如,map是1个常常的措施定义,若是没有展开柯里化。

def map[A, B](a: A, f: A => B): B = f(a)

当直接传送匿名函数时,假设没有显式地标明参数类型,类型推演将发出错误。

map("HORANCE", s => s.toLowerCase)  // Error

它必须显式地声称匿名函数的入参类型。

map("HORANCE", (s: String) => s.toLowerCase)

如果对map开展柯里化,让其有着多个柯里化的参数列表。

def map[A, B](a: A)(f: A => B): B = f(a)

当传递匿名函数时,它便得以活动落成匿名函数参数的项目推演。

map("HORANCE", s => s.toLowerCase)

函数的花色

在scala中,函数类型的格式为 A =>
B,表示一个:接受参数类型为A的、并回到类型B的函数
eg:
Int => String 是把整型映射为字符串的函数类型

决定抽象

借助柯里化,可以兑现控制结构的肤浅。例如,可以安排3个指令式的until的函数,它将循环调用blok,直至条件cond为真得了(与while相反)。

@annotation.tailrec
def until(cond: => Boolean)(block: => Unit) {
  if (!cond) {
    block
    until(cond)(block)
  }
}

当调用until时,最终贰个柯里化参数列表的调用可以利用大括号代替小括号。例如,

var n = 10
var r = 0
until (n == 0) {
  r += n
  n -= 1
}

实际上,它等于于

(0 to 10).sum

前端为指令式的代码风格,后者为函数式的吩咐风格。

高阶函数

  1. 接受的参数为函数

def funcName( f: (Int, Int) => Int) = {
    f(4,4)
}

参数:f
类型:Int => Int
返回:Int类型

  1. 重回值为3个函数

def funcName() = ( name: String) => {"hello "+name}

参数:name
类型:String => String
返回:String类型
瞩目上述叫做:匿名函数 – 函数常量 – 函数的文字量(相对于def funcName
叫函数变量)

  1. 富有上述意况

形式化

诚如地,具有几个参数列表,并且每种参数列表中有且仅有3个参数的「柯里化」常称为「完全柯里化」。尽管存在三个截然柯里化的法子定义,它兼具如下的款式:

def f(a1: A1)(a2: A2)...(an: An):R = E

其中,n > 1。它等价于:

def f(a1: A1)(a2: A2)...(an_1: An_1) = an => E

递归这些进程,可以生产最后的对等方式:

def f = (a1 => (a2 => ...(an => E)...))

固然,后者也是多少个施用def概念的办法,但它回到了三个「完全柯里化的函数类型」。依照规矩,后者也常称为「完全柯里化的函数」。

匿名函数

匿名函数没有函数名
定义格式: (形参列表) => { 函数体 }

  • (x: Int) => { x * x }
  • (x: Int,y: Int) => { x + y }
  • var add = (x: Int,y: Int) => { x + y }
  • def func() = (x: Int,y: Int) => { x + y }

scala> (x: Int,y: Int) => { x + y }
res0: (Int, Int) => Int = $$Lambda$1016/2093139281@69feb4d9

scala> var add = (x: Int,y: Int) => { x + y }
add: (Int, Int) => Int = $$Lambda$1025/1152113439@62108cd3

scala> add(1,2)
res1: Int = 3

scala> def funcName() = ( name: String) => {"hello "+name}
funcName: ()String => String

scala> funcName()("Jack")
res4: String = hello Jack

柯里化方法与函数

def concat(s1: String)(s2: String): String = s1 + s2
def concat: String => String => String = s1 => s2 => s1 + s2

依照上述格局化的讲述,前者举行2次变换可将其转移为子孙后代,两者成效等价。不过,它们两者之间是存在着神秘的差别,前者是三个艺术,它没有值;而后人是二个函数,且持有函数值。

柯里化

Scala中的主要的技能,具有多少个参数的函数转化成二个函数列,每一个函数只有单纯参数

def add(x: Int,y: Int) = { x + y }  //普通函数定义
def add(x: Int)(y: Int) = { x + y } //柯里化函数定义,多个参数单一化,串接起来

def curriedAdd(a: Int)(b: Int) = a + b
curriedAdd(2)(2) //4
val add = curriedAdd(1)_    //Int => Int
add(2)  //3

解释:curriedAdd(1)_,下划线统配之后的一切参数列表,此处a=1固定,唯有b是可变值,下划线通配变量b
add(2),传入curriedAdd后a=1,b=2

接纳柯里化技术,通过原有通用函数构造一些新的函数

柯里化方法

首先,对于柯里化的措施,它首先是两个办法(Method)。

def concat(s1: String)(s2: String): String = s1 + s2

就此,它没有值。如下语句将暴发编译错误。

val f = concat  // Error

可是,可以接纳η扩展将该柯里化的章程转变为柯里化的函数值。

val f = concat _  // f: String => (String => String) = <function1>

如果一定第二个参数的值,必须再实施η壮大,才能取得部分使用函数。

val f = concat("horance") _  // f: String => String = <function1>

Scala中的递归

scala里计算n的阶乘

def factorial(n: Int): Int = 
    if(n <= 0) 1
    else n * factorial(n - 1)

递归优化:变成尾递归,尾递归会复写当前栈,不会招致堆栈溢出
尾递归优化:用¥annotation.tailrec显示指明编译时举办尾递归优化

@annotation.tailrec
def factorial(n: Int,m: Int): Int = 
    if(n <= 0) m
    else factorial(n - 1, m * n)
factorial(5,1)

上述引入m,m保留当前运算从前的历史阶乘结果
若是退出,则就是递归的值,若是不脱离,那么把近期结果传到下五回,那样不要求开辟栈保留统计结果,每便只需m变量记录结果
示例:求f(x)在(a,b)上的和

    def sum(f: Int => Int)(a: Int)(b: Int): Int = {

            @annotation.tailrec
            def loop(n: Int, acc: Int): Int = {
            //函数中可定义其他函数
            //n:循环变量
            //acc:累积和
                    if (n > b) {
                            println(s"n=${n}, acc=${acc}")
                            acc
                            //if表达式返回acc的值
                    } else {
                            println(s"n=${n}, acc=${acc}")
                            loop(n + 1, acc + f(n))
                            //else表达式返回loop
                    }
            }
            loop(a, 0)
    }                               //> sum: (f: Int => Int)(a: Int)(b: Int)Int
    //函数y=f(x)
    sum(x => x)(1)(5)               //> n=1, acc=0
                                    //| n=2, acc=1
                                    //| n=3, acc=3
                                    //| n=4, acc=6
                                    //| n=5, acc=10
                                    //| n=6, acc=15
                                    //| res0: Int = 15


    sum(x => x * x)(1)(5)           //> n=1, acc=0
                                    //| n=2, acc=1
                                    //| n=3, acc=5
                                    //| n=4, acc=14
                                    //| n=5, acc=30
                                    //| n=6, acc=55
                                    //| res1: Int = 55



    sum(x => x * x * x)(1)(5)       //> n=1, acc=0
                                    //| n=2, acc=1
                                    //| n=3, acc=9
                                    //| n=4, acc=36
                                    //| n=5, acc=100
                                    //| n=6, acc=225
                                    //| res2: Int = 225

    //利用柯里化技术,通过原有通用函数构造一些新的函数,简化代码

    val sumSquare = sum(x => x * x)_              
    //> sumSquare  : Int => (Int => Int) = sumfunc$$$Lambda$13/2054798982@34ce8af7

    sumSquare(1)(5)                 //> n=1, acc=0
                                    //| n=2, acc=1
                                    //| n=3, acc=5
                                    //| n=4, acc=14
                                    //| n=5, acc=30
                                    //| n=6, acc=55
                                    //| res3: Int = 55

柯里化函数

而对此柯里化的函数,它是二个函数(Function)。

def concat: String => String => String = s1 => s2 => s1 + s2

无需η扩展,它便可以一向拿走到函数值。

val f = concat    // f: String => (String => String) = <function1>

假设固定第2个参数的值,也不用实践η增添,便径直可得到的有些采取函数。

val g = concat("horance")  // g: String => String = <function1>

正文永久链接:

https://www.zybuluo.com/EVA001/note/926604


自行扩张

理所当然,即便存在贰个高阶函数twice,它接受String => String => String的函数类型。

def twice[A](x: A)(f: A => A => A): A = f(x)(x)

对于柯里化的方法concat

def concat(s1: String)(s2: String): String = s1 + s2

能够直接传送concattwice,编译器自动落成η扩展。

twice("horance")(concat)

事实上,它等价于:

twice("horance")(concat _)

FunctionN.curried(N >= 2)

一般地,完全柯里化的函数具有如下的门类。

(A1 => (A2 => ...(An => E)...))

可以经过调用FunctionN.curried方法,将3个「非完全柯里化的函数」转化为等价的「完全柯里化的函数」。例如,若是concat是1个平常的措施定义。

def concat(s1: String, s2: String): String = s1 + s2

首先,应用η扩张,将其变化为部分应用函数;此时等到的「部分采取函数」还不是二个完全被柯里化的函数。

val f = concat _    // f: (String, String) => String = <function2>

可以在这些实例对象上经过调用curried将其变动为「完全柯里化」的极度格局。

val g = f.curried  // g: String => (String => String) = <function1>

其中,curried是三个高阶函数,它回到一个一心柯里化的函数。

trait Function2[-T1, -T2, +R] {
  def apply(v1: T1, v2: T2): R

  def curried: T1 => T2 => R =
    x1 => x2 => apply(x1, x2)
}

Function.uncurried

可以对g应用Function.uncurried的逆向操作,将其生成为「非柯里化」的函数。

val f = Function.uncurried(g)   // f: (String, String) => String = <function2>

其中,Function.uncurried是如此定义的。

object Function {
  def uncurried[A1, A2, B](f: A1 => A2 => B): (A1, A2) => B =
    (x1, x2) => f(x1)(x2)
}

FunctionN.tupled(N >= 2)

比方存在壹个二元组。

val names = ("horance", "liu")

一旦要调用如下的函数,完毕字符串的连年。

def concat(s1: String, s2: String): String = s1 + s2

它需求各样取出元组中的成分并展开传递。

val fullName = concat(names._1, names._2)   // fullName: String = horanceliu

可以运用Function2.tupled改正安排。首先,应用η扩老马该方法变换为一些应用函数,然后再在该函数对象上调用tupled,并间接传送二元组。

val fullName = (concat _).tupled(names)   // fullName: String = horanceliu

其中,tupled是二个高阶函数,它回到(String, String) => String系列的函数。

trait Function2[-T1, -T2, +R] {
  def apply(v1: T1, v2: T2): R

  def tupled: Tuple2[T1, T2] => R = {
    case Tuple2(x1, x2) => apply(x1, x2)
  }
}

Function.untupled

可以对g应用Function.untupled的逆向操作,将承受元组类型参数的函数转变为接受参数列表的函数。

val g = (concat _).tupled     // g: ((String, String)) => String = <function1>
val f = Function.untupled(g)  // f: (String, String) => String = <function2>

其中,Function.untupled是这么定义的。

object Function {
  def untupled[A1, A2, B](f: (A1, A2) => B): (A1, A2) => B = 
    (x1, x2) => f((x1, x2))
}

实战ScalaHamcrest

对此自由的种类AA => Boolean每每称为「谓词」;尽管该谓词用于匹配类型A的有些值,也时不时称该谓词为「匹配器」。

ScalaHamcrest是三个使用函数式设计思想完成的Matcher集合库。通过实战ScalaHamcrest,以便加深精通Scala函数的论争功底。

原子匹配器

可以定义特定的原子匹配器。例如:

def always: Any => Boolean = _ => true
def never:  Any => Boolean = _ => false

也足以定义equalTo的原子匹配器,用于比较对象间的相等性。

def equalTo[T](expected: T): T => Boolean =
  expected == _

equalTo接近,可以定义原子匹配器same,用于比较对象间的一致性。

def same[T <: AnyRef](t: T): T => Boolean = t eq _

其中,T <: AnyRef类型T开展界定,排除AnyVal的子类误操作same。类似于类型上界,也得以使用其余的花色界定形式;例如,可以定义instanceOf,对类型T举行上下文界定,用于匹配有个别实例的类型。

def instanceOf[T : ClassTag]: Any => Boolean = x =>
  x match {
    case _: T => true
    case _    => false
  }

突发性,基于既有的原子可以很有利地结构出新的原子。

def nil   = equalTo[AnyRef](null)
def empty = equalTo("")

组成匹配器

也足以将各样原子恐怕组合器进行组装,形成威力更为强劲的组合器。

def allOf[T](matchers: (T => Boolean)*): T => Boolean =
  actual => matchers.forall(_(actual))

def anyOf[T](matchers: (T => Boolean)*): T => Boolean =
  actual => matchers.exists(_(actual))

特殊地,基于anyof,可以社团很多一定的匹配器。

def blank: String => Boolean = """\\\\s*""".r.pattern.matcher(_).matches

def emptyOrNil = anyOf(nil, equalTo(""))
def blankOrNil = anyOf(nil, blank)

修饰匹配器

修饰也是一种奇特的构成行为,用于落成既有效果的狠抓和互补。

def not[T](matcher: T => Boolean): T => Boolean =
  !matcher(_)

def is[T](matcher: T => Boolean): T => Boolean =
  matcher

其中,not, is是三个广大的修饰器,可以修饰任意的匹配器。其它,可以由此定义语法糖,进步用户感受。例如,可以运用not替换not(equalTo)is替代is(equalTo),不仅减轻用户的担当,而且还是能增强表明力。

def not[T](expected: T): T => Boolean = not(equalTo(expected))
def is[T](expected: T):  T => Boolean = is(equalTo(expected))

测试用例

于今,还不知底ScalaHamcrest哪些使用啊?可以定义一个实用方法assertThat

def assertThat[A](actual: A, matcher: A => Boolean) =
  assert(matcher(actual))

其中,assert定义于Predef内部。例如存在如下二个测试用例。

assertThat(2, allOf(always, instanceOf[Int], is(2), equalTo(2)))

压实特性

遗留七个很有意思的题材,能不能使用&&直白连接八个匹配器形成调用链,替代allOf匹配器呢?

assertThat("horance", equalTo("horance") && ignoringCase(equalTo)("HORANCE"))

可以定义个一个implicit Matcher,并添加了&&, ||, !的基本操作,用于模拟谓词的基本效能。

implicit class Matcher[-A](pred: A => Boolean) extends (A => Boolean) {
  self =>

  def &&[A1 <: A](that: A1 => Boolean): A1 => Boolean =
    x => self(x) && that(x)

  def ||[A1 <: A](that: A1 => Boolean): A1 => Boolean =
    x => self(x) || that(x)

  def unary_![A1 <: A]: A1 => Boolean =
    !self(_)

  def apply(x: A): Boolean = pred(x)
}

总结

本文重点叙述了Scala函数式编程的基础,重点谈论了多少个简单混淆的定义。包含「方法」与「函数」,「偏函数」与「部分行使函数」,「柯里化」,「高阶函数」等,并通过实战ScalaHamcrest,深刻领悟Scala函数的基本概念。

接下去,将重大谈论函数式编程的接纳。

相关文章