>

cur)澳门博发娱乐官网是最后一条没有增加任何运

- 编辑:澳门博发娱乐官网 -

cur)澳门博发娱乐官网是最后一条没有增加任何运

接着昨天的文章,再示范一个稍微复杂一点的尾递归tail recursion例子:计算第n个Fibonacci数。Fibonacci数第一、第二个数值分别是0,1,按顺序后面的数值是前面两个数的加合。例如:0,1,1,2,3,5...

既然是泛函编程,多了解一下函数自然是免不了的了:

推荐学习视频:慕课网

推荐学习视频:慕课网

 1 def fib: Int = { 2     @annotation.tailrec 3       def go(cnt: Int, prev: Int, cur: Int): Int = cnt match { 4         case m if (m < 0 ) => sys.error("Negative Number Not Allowed!") 5         case 0 => prev 6         case c => go(cnt-1,cur, prev + cur) 7       } 8       go(n,0,1) 9   }                                               //> fib: Int10   fib                                          //> res52: Int = 5

方法(Method)不等于函数(Function)

ScalaAPI:

ScalaAPI:

首先,尾递归是指一个递归函数最后一个语句独立引用了自己。在以上的例子里 go(cnt-1,cur,prev + cur)是最后一条没有增加任何运算的独立语句。我们可以试着约化:

方法不是函数但可以转化成函数;可以手工转换或者由编译器(compiler)在适当的情况下自动转换。反向转换则不然;函数是无法转换到方法的。先看看下面的例子:



1 fib(5)2   go(5,0,1)3   go                                         = go(4,1,1)4   go,1+                                 = go(3,1,2)5   go,+                       = go(2,2,3)6   go+,++    = go(1,3,5)7   go => 5
1 scala> def aMethod(x: Int): Int = x + 10
2 aMethod: (x: Int)Int
3 
4 scala> val aFunction = (x: Int) => x + 10
5 aFunction: Int => Int = <function1>

简介

简介

正是我们预期的答案。

aMethod 与 aFunction 在类型上是不同的。再看看下面:



Scala的函数还是值得提的。函数可以当作标准的对象使用:可以当作另一个函数的输入参数或者结果值。接受函数作为输入参数或者返回另一函数作为结果的函数被称之为高阶函数(high order function)。在Scala编程里匿名函数(anonymous function or lamda function)或函数文本(function literal)的使用也很普遍。用书上的代码样例来示范:

1 scala> aFunction
2 res0: Int => Int = <function1>
3 
4 scala> aMethod
5 <console>:9: error: missing arguments for method aMethod;
6 follow this method with `_' if you want to treat it as a partially applied function
7               aMethod

■函数式编程是什么鬼?

■函数式编程是什么鬼?

1 def formatResult(name: String, n: Int, f: Int => Int) = {2   val msg = "The %s of %d is %d."3   msg.format4 }

引用方法必须提供完整的参数清单,引用函数则无须。把方法转换成函数呢?在参数位置用 _ 来进行转换:

就是只用纯函数来编写程序

就是只用纯函数来编写程序

注意formatResult是一个高阶函数,因为它接受一个函数f作为输入参数。这里 Int => Int 是一个类声明,是个函数的类型。看看高阶函数和匿名函数是怎么使用的:

1 scala> val toFunction = aMethod _
2 toFunction: Int => Int = <function1>
3 
4 scala> toFunction
5 res2: Int => Int = <function1>

■函数式编程的重要概念

■函数式编程的重要概念

1 def main(args: Array[String]): Unit = {2   println(formatResult("absolute value", -42, abs))3   println(formatResult("factorial", 7, factorial))4   println(formatResult("increment", 7,  => x + 1))5   println(formatResult("increment2", 7,  => x + 1))6   println(formatResult("increment3", 7, x => x + 1))7   println(formatResult("increment4", 7, _ + 1))8   println(formatResult("increment5", 7, x => { val r = x + 1; r }))9 }

aMethod转换成函数toFunctions后具备了函数的特性。

•纯函数(Pure Function)、或函数的纯粹性(Purity)、没有副作用(Side Effect)

•纯函数(Pure Function)、或函数的纯粹性、没有副作用(Side Effect)

传入函数formatResult的输入参数f可以是一个普通的函数如factorial,abs。也可用函数文本,只要它的类型是Int => Int就可以了。以上匿名函数的各种表述形式可以参考一下Scala语言教程。

我们称函数为“头等类值”(first class value),可以当作高阶函数的参数或返回值。但方法不是“头等类值”,不能当作参数。那么怎么解释下面例子里的代码呢?

副作用是状态的变化(Mutation)

副作用是状态的变化

1 def main(args: Array[String]): Unit = {
2   println(formatResult("absolute value", -42, abs))
3   println(formatResult("factorial", 7, factorial))
4   println(formatResult("increment", 7, (x: Int) => x + 1))
5   println(formatResult("increment2", 7, (x) => x + 1))
6   println(formatResult("increment3", 7, x => x + 1))
7   println(formatResult("increment4", 7, _ + 1))
8   println(formatResult("increment5", 7, x => { val r = x + 1; r }))
9 }

例如:修改全局变量、跑出异常、IO读写、调用邮副作用的函数

例如:修改全局变量、跑出异常、IO读写、调用邮副作用的函数

在这里abs, factorial都是方法。传入高阶函数formatResult中能行吗?下面是运行后的结果:

•引用透明(Referential Transparency)

•引用透明(Referential Transparency)

1 The absolute value of -42 is 42
2 The factorial of 7 is 5040
3 The increment of 7 is 8
4 The increment2 of 7 is 8
5 The increment3 of 7 is 8
6 The increment4 of 7 is 8
7 The increment5 of 7 is 8

对于相同的输入,总是得到相同的输出

对于相同的输入,总是得到相同的输出

没出错呀。难道方法是可以当作传入参数的吗?实际上这段程序在编译的时候由编译器自动进行了转换。Scala的编译器能针对需要函数的地方把方法转换成函数。

如果f(x)的参数x和函数体都是引用透明的,那么函数f就是纯函数

如果f的参数x和函数体都是引用透明的,那么函数f就是纯函数

 

•不变性(Immutability)

•不变性(Immutability)

函数就是普通的对象

为了获得引用透明性,任何值都不能变化

为了获得引用透明性,任何值都不能变化

下面是一个函数文本:

•函数式一等公民(First-class Function):

•函数式一等公民(First-class Function):

1 scala> (a: Int, b: Int) => a + b
2 res4: (Int, Int) => Int = <function2>

一切都是计算、函数式编程中只有表达式,变量,函数都是表达式

一切都是计算、函数式编程中只有表达式,变量,函数都是表达式

编译时编译器会把它转换成下面的代码:

即:在程序中用变量的地方都可以使用函数。

即:在程序中用变量的地方都可以使用函数。

1 val addThem = new Function2[Int, Int, Int] {
2   def apply(a: Int, b: Int) = a + b
3 }

•高阶函数(Higher order Function)

•高阶函数(Higher order Function)

这里Function2是Scala语言标准类对象,res4(1+2) ===>  addThem.apply(1,2)

•闭包(Closure)

•闭包

 

•表达式求值策略:严格求职 和非严格求职

•表达式求值策略:严格求职 和非严格求职

多态函数

(Call By Value vs. Call By Name)

(Call By Value vs. Call By Name)

为了示范Scala的多态函数,我们先从下面的一个例子开始:从一个整数数组中找出第一个匹配数的位置:

•惰性求值(Lazy Evaluation):就是说当定义这个表达式的时候不会立即去求值,只有当第一次用到这个表达式的时候才会去求值。

•惰性求值(Lazy Evaluation):就是说当定义这个表达式的时候不会立即去求值,只有当第一次用到这个表达式的时候才会去求值。

 1  def findFirstInt(arr: Array[Int], target: Int): Int = {
 2       def loop(idx: Int): Int = idx match {
 3           case l if (l >= arr.length) => -1          //indicate not found
 4           case i if (arr(i) == target) => idx
 5           case _ => loop(idx + 1)
 6       }
 7       loop(0)
 8   }                                               //> findFirst: (arr: Array[Int], target: Int)Int
 9   findFirstInt(Array(2,4,3,9,0),3)                //> res53: Int = 2
10   findFirstInt(Array(2,4,3,9,0),7)                //> res54: Int = -1

•递归函数(Recursive Function):函数式编程中是没有循环语句的,所有的循环都是用递归来实现。

•递归函数(Recursive Function):函数式编程中是没有循环语句的,所有的循环都是用递归来实现。

从一个字串数组中找出第一个匹配字串的位置:

•尾递归(Tail Recursion)

•尾递归(Tail Recursion)

 1   def findFirstString(arr: Array[String], target: String): Int = {
 2       def loop(idx: Int): Int = idx match {
 3           case l if (l >= arr.length) => -1        //indicate not found
 4           case i if (arr(i) == target) => idx
 5           case _ => loop(idx + 1)
 6     }
 7       loop(0)
 8   }                                               //> findFirstString: (arr: Array[String], target: String)Int
 9   findFirstString(Array("Hello","My","World"),"My")
10                                                   //> res55: Int = 1
11   findFirstString(Array("Hello","My","World"),"Yours")
12                                                   //> res56: Int = -1

 

■函数式编程的优点

这个函数与上面整数数组例子有许多相似之处,或者说基本上是一致的。这样我们可以通过多态函数把共通点抽象出来:

■函数式编程的优点

生产效率高

 

生产效率高

同样的功能的程序,Lisp代码的长度可能是C代码的1/7~1/10

 1 def findFirstA[A](arr: Array[A],target: A)(equ: (A,A) => Boolean): Int = {
 2         def loop(idx: Int): Int = idx match {
 3           case l if (l >= arr.length) => -1    //indicate not found
 4           case i if (equ(arr(i),target)) => idx
 5           case _ => loop(idx + 1)
 6     }
 7       loop(0)
 8   
 9   }                                           //> findFirstA: [A](arr: Array[A], target: A)(equ: (A, A) => Boolean)Int
10   findFirstA[Int](Array(2,4,3,9,0),3)((x,y) => x == y)
11                                               //> res57: Int = 2
12   findFirstA[String](Array("Hello","My","World"),"My")((x,y) => x == y)
13                                               //> res58: Int = 1

同样的功能的程序,Lisp代码的长度可能是C代码的1/7~1/10

易于推理(Reasoning)

findFirstA是个多态函数。A是一个类型变量。我们可以说findFirstA是个针对类型变量A的多态函数。注意我们在findFirstA增加了一个参数清单- (equ: (A,A) => Boolean)。这是因为我们还无法确定A的类型。那么我们必须提供A类型的对比函数。我们可以用findFirstA针对整数、字串进行操作。我们也可以对其它类型进行操作,只要我们能提供那种类型的比较函数。

易于推理(Reasoning)

并行编程

泛函编程说的白点就是摆弄函数。把函数摆过来弄过去的就完成了编程的过程。从下面的例子可以一探端倪:

并行编程

多核计算、云计算

纯函数是可以部分作用(partially apply)的:对一个多入参函数可以分多次每次作用(apply)一个参数

多核计算、云计算


1 def partialApply[A,B,C](a: A, f: (B,C) => C): B => C

 

Scala安装

通过函数partialApply可以把一个两个入参的函数f分分两次作用它的参数:引用partialApply是作用参数a,形成一个需要参数B的函数。



两个参数作用(apply)了其中一个,所以称为部分作用。该如何实现:

Scala安装

■JDK:建议Java 7以上)

1   def partialApply[A,B,C](a: A, f: (A,B) => C): B => C = (b: B) => f(a,b)
2                                                   //> partialApply: [A, B, C](a: A, f: (A, B) => C)B => C

下载地址:

我们知道partialApply的结果是一个入参B返回C的函数。所以想办法从匹配类型款式上着手。可以直接用一个函数文本表达这个结果:给我一个B=b,我返回给你一个C=f(a,b);一个典型的lambda表达式。用一个实际的例子来示范:

■JDK:建议Java 7以上)

1 def addTwoParams(a: Int, b: Int) = a + b        //> addTwoParams: (a: Int, b: Int)Int
2   addTwoParams(2,5)                               //> res59: Int = 7
3   val applyOnce = partialApply(2,addTwoParams)    //> applyOnce  : Int => Int = <function1>
4   applyOnce(5)                                    //> res60: Int = 7

下载地址:

addTwoParams是一个两个入参的函数,applyOnce是向addTwoParams作用了一个参数2后产生的函数,再用参数5再对applyOnce作用后结果等于7.

addTwoParams(2,5)。为什么费那么大的劲把函数变来变去呢?实际上这种函数变形在泛函编程中很普遍,是函数组合(Functional Composition)必须掌握的技巧。

函数变形在泛函编程中是常用的技巧。下面的Curry function就是把一个N个输入参数的函数变成一个参数的N次作用:

f(a,b,c,...n) = f(a)(b)(c)...(n) = a => b => c => ... => n

1 def curryTwo[A,B,C](f: (A,B) => C): A => (B => C) 

函数curryTwo把一个两个参数的函数转变成一个参数返回另一个同样是一个参数的函数。用函数文本实现curryTwo后再使用一下来示范(注意返回的类型款式):

1 def curryTwo[A,B,C](f: (A,B) => C): A => (B => C) = (a: A) => ((b: B) => f(a,b))
2                                                   //> curryTwo: [A, B, C](f: (A, B) => C)A => (B => C)
3   val curriedFunction = curry(addTwoParams)       //> curriedFunction  : Int => (Int => Int) = <function1>
4   val curryOnce = curriedFunction(2)              //> curryOnce  : Int => Int = <function1>
5   curryOnce(5)                                    //> res61: Int = 7

把addTwoParams转成curriedFunction Int=>(Int=>Int)。可以马上看到需要填写两次输入参数。我们遇到这种函数变形的问题时通常会用函数文本尝试匹配函数的结果类型款式(type signature)。

uncurry是curry的反向:把curry函数返还到多参数函数:a => b => c => ... => n  = f(a,b,c,...n)

1 def uncurry[A,B,C](f: A => B => C): (A,B) => C

结合上面的例子示范uncurry函数实现和使用:

1   def uncurry[A,B,C](f: A => B => C): (A,B) => C = (a: A, b: B) => (f(a))(b)
2                                                   //> uncurry: [A, B, C](f: A => (B => C))(A, B) => C
3   val uncurriedFunction = uncurry(curriedFunction)//> uncurriedFunction  : (Int, Int) => Int = <function2>
4   uncurriedFunction(2,5)                          //> res62: Int = 7

uncurriedFunction又变回去了。

 最后,示范一个函数组合的例子:

1 def compose[A,B,C](f: B => C, g: A => B): A => C

compose是将f和g两个函数组合成另一个函数。看看下面实现和使用示范:

1 def compose[A,B,C](f: B => C, g: A => B): A => C = (a: A) => f(g(a))
2                                                   //> compose: [A, B, C](f: B => C, g: A => B)A => C
3   val fadd = (x: Int) => x + 2                    //> fadd  : Int => Int = <function1>
4   val fmul = (y: Int) => y * 5                    //> fmul  : Int => Int = <function1>
5   val mulThenAdd = compose(fadd,fmul)             //> mulThenAdd  : Int => Int = <function1>
6   mulThenAdd(2)                                   //> res63: Int = 12

把fadd,fmul组合起来形成了一个新的函数。mulThenAdd(2) = (2 * 5) + 2

如果再写的形象一点:

1   (fadd compose fmul)(2)                          //> res64: Int = 12
2   (fmul compose fadd)(2)                          //> res65: Int = 20

注意compose右边关联的(right hand associate):fadd compose fmul 中先运算fmul把结果输入fadd进行运算。设计另一个左边关联函数andThen:

1 def andThen[A,B,C](f: A => B, g: B => C): A => C = (a: A) => g(f(a))
2                                                   //> andThen: [A, B, C](f: A => B, g: B => C)A => C
3  (fadd andThen fmul)(2)                           //> res66: Int = 20

想想这里面的意义:fadd和fmul可能都是几千行代码的大函数,而我们能够很简洁地把它们连接起来,只需要把类型匹配起来就行了。

 

 

 

 

 

 

 

 

 

 

■Scala 2.11

下载地址:

■Scala 2.11

■编译工具SBT

下载地址:

下载地址:

■编译工具SBT

■交互式程序运行环境REPL(轻量级,用于实验,Read Evaluate Print Loop)

下载地址:

■IDE

■交互式程序运行环境REPL(轻量级,用于实验,Read Evaluate Print Loop)

下载地址:

■IDE

安装成功之后,在命令行窗口执行以下命令应该可以正确执行(最好重新启动命令行窗口)

下载地址:

java -version

 

scala -version

安装成功之后,在命令行窗口执行以下命令应该可以正确执行(最好重新启动命令行窗口)


java -version

基础篇

scala -version


 

■变量


三种变量修饰符

基础篇

val 定义immutable variable。也即是常量


var 定义mutable variable。也就是变量

■变量

lazy val。定义惰性求值的常量

三种变量修饰符

*可以不显示指定变量的类型,因为Scala会自动进行类型推倒

本文由胜博发-编程发布,转载请注明来源:cur)澳门博发娱乐官网是最后一条没有增加任何运