函数式编程
本篇来进修Go说话的函数式编程,函数式编程不是Go说话独有的,像Python也是支撑函数式编程的,不外Go说话支撑函数式编程首要施展在闭包上。
Go说话闭包应用:1)不需要润饰若何接见自由变量;2)没有Lambda表达式,然则有匿名函数 (其实两者差不多)。
接下来谈一谈函数式编程和函数指针的区别,其实我小我更倾向于函数式编程,因为在函数式编程(如Python)中,函数是一等公民,是以参数,变量及返回值都能够是函数;而像函数指针(如C++、Java)函数只是一个名字,其实就是指针。
在函数式编程中,有一个高阶函数的概念,也就是说一个函数能够作为参数传给此外一个函数,或许一个函数的返回值为此外一个函数(若返回值为该函数自己,则为递归),知足其一则为高阶函数,如python中的map,reduce,filter等。还有就是闭包这个概念。
当然或者有人要拿出"正统"函数式编程来说话了,需要知足两点:1)弗成变性:不克有状况,只有常量和函数;2)函数只能有一个参数。
这个"正统"函数式编程要求里面不克有变量,只有常量和函数这两种,甚至连选择、轮回语句都不克使用;更过度的要求是参数只能有一个参数,之前的参数列表都不克用了,太特么失常了吧。因为Go说话设计时要求按照了这个划定,然则实际上天真性很大,能够不按照上面"正统"函数式编程的要求来。
下面连系一个例子解说Go说话的函数式编程:较量1+2+3+...+9=?我们先用通俗的方式,接着使用函数式编程,然后试着体味两者的分歧之处。
通俗方式实现的代码如下,这个其实非常简洁:
package main
import "fmt"
//界说乞降函数,测试函数式编程
func test(n int)int{
sum:=
for i:=0;i sum+=i
}
return sum
}
func main() {
fmt.Println(test(10))
}
//运行究竟:
45
接下来看一下若何使用函数式编程来实现这个功能:
package main
import "fmt"
//界说乞降函数,测试函数式编程
func functionaltest()func(int)int{
sum:=0
return func(v int) int {
sum+=v
return sum
}
}
func main() {
a:=functionaltest()
for i:=0;i<10;i++{
fmt.Printf("0+1+...+%d=%d",i,a(i))
}
}
//运行究竟:
0+1+...+0=0
0+1+...+1=1
0+1+...+2=3
0+1+...+3=6
0+1+...+4=10
0+1+...+5=15
0+1+...+6=21
0+1+...+7=28
0+1+...+8=36
0+1+...+9=45
其实上面就是闭包,在函数体中包含自由变量和局部变量,这里的sum就是自由变量,v是局部变量。
下面是我从网上找的其他说话若何经由闭包来实现响应的功能:
1)Python中的闭包:python原生支撑闭包、使用_closure_来查察闭包内容
def test():
sum = 0
def f(value):
nonlocal sum
sum += value
return sum
return f
2)C++中的闭包:曩昔stl或许boost带有雷同库;C++11及今后:支撑闭包,以下是C++14下编译经由的:
auto test(){
auto sum = 0;
return [-] (int value) mutable {
sum += value;
return sum;
}
}
3)Java中的闭包:1.8今后使用Function接口和Lambda表达式来建立函数对象,函数自己不克作为参数和返回值的;1.8以前匿名类或Lambda表达式均支撑闭包
Function test() {
final Holder sum = new Holder<>(0);
return (Integer value) -> {
sum.value += value;
return sum.value;
}
}
还有一个问题就是前面说的"正统"函数式编程要求:1)弗成变性:不克有状况,只有常量和函数;2)函数只能有一个参数。我们测验使用代码来实现这个要求,然则实现正统函数式编程不克有状况,那么应该将状况(函数执行究竟)放在另一个函数中:
//使用正统函数式编程,只有常量和函数,没有变量
type itest func(int)(int,itest)
func ftest(base int)itest{
return func(v int) (int, itest) {
return base+v,ftest(base+v)
}
}
func main() {
a:=ftest(0)
for i:=0;i<10;i++{
var s int
s,a =a(i)
fmt.Printf("0+1+...+%d=%d",i,s)
}
}
//运行究竟:
0+1+...+0=0
0+1+...+1=1
0+1+...+2=3
0+1+...+3=6
0+1+...+4=10
0+1+...+5=15
0+1+...+6=21
0+1+...+7=28
0+1+...+8=36
0+1+...+9=45
不外这种正统函数式编程懂得起来非常难题,写起来也不轻易懂得。
斐波那契数列懂得闭包
接下来经由斐波那契数列来加深本身对于闭包的懂得,同样先使用通俗方式,然后使用闭包的体式实现。
通俗方式实现输出斐波那契数列:
//chapter06/fibonaqitest/fibonaqi.go文件
package fibonaqi
//1,1,2,3,5,8,13,21...
func FBtest(n int)int{
a,b:=0,1
for i:=0;i a,b = b,a+b
}
return a
}
//chapter06/fibonaqitest/main.go文件:
package main
import (
"chapter06/fibonaqitest/fibonaqi"
"fmt"
)
func main() {
fmt.Println(fibonaqi.FBtest(5))
}
//运行究竟:
5
再来试试闭包的实现:
//chapter06/fibonaqitest/fibonaqi.go文件
//闭包
func FPtest()func()int{
a, b:=0,1
return func() int {
a,b=b,a+b
return a
}
}
然则这边有一个问题,就是这个函数内无法判断何时输出。其实这种和生成器非常相似,是以每次挪用会执行一次该函数:
func main() {
f:=fibonaqi.FPtest()
fmt.Println(f()) //闭包函数测试
fmt.Println(f())
}
//运行究竟:
1
1
然则这个闭包其实酿成了生成器,若是我们想输出斐波那契数列中小于10000的元素,我们需要多次挪用这个生成器,直到输出的元素小于10000才住手运行,那么有没有简洁的方式呢?我们能够让这个斐波那契函数实现一个输出内容的接口就行了:
//闭包
func FPtest()func()int{
a, b:=0,1
return func() int {
a,b = b,a+b
return a
}
}
type IntGenerator func() int
func (g IntGenerator)Read(p[]byte)(n int,err error){
next:=g() //获取下一个元素
if next >10000{ //达到10000以上竣事
return 0, io.EOF
}
s:=fmt.Sprintf("%d",next) //转换成字符串
// TODO: incorrect if p is too small!
return strings.NewReader(s).Read(p)
}
//之前用于从文件中输出内容的函数
func PrintFileContent(reader io.Reader){
scanner:=bufio.NewScanner(reader)
for scanner.Scan(){
fmt.Println(scanner.Text())
}
}
func main() {
var ft fibonaqi.IntGenerator =fibonaqi.FPtest()
fibonaqi.PrintFileContent(ft)
}
//运行究竟:
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
不外这个代码有一个瑕疵就是这个p对象不克太小,太小就无法输出信息,后续会对这段代码进行点窜。
二分搜刮树遍历懂得闭包
接下来使用之前介绍的二分搜刮树遍历的例子来加深对闭包的懂得。学过二分搜刮树的人一定知道中序遍历究竟是0 9 2 0 6:
然则之前的遍历函数只能实现遍历的功能,接下来让函数实现接口,那它就能干好多事了:
//函数闭包,演示二分搜刮树的遍历
func (node *treeNode)Traverse(){
node.TraverseFunc(func(n *treeNode) {
n.Print()
})
fmt.Println()
}
func (node *treeNode)TraverseFunc(f func(*treeNode)){
if node==nil{
return
}
node.left.TraverseFunc(f)
f(node)
node.right.TraverseFunc(f)
}
func main() {
var root treeNode //声明一个二分搜刮树对象
root = treeNode{value: 2} //二分搜刮树root节点初始化
root.left = &treeNode{} //二分搜刮树root节点左子树初始化
root.right = &treeNode{6, nil, nil} //二分搜刮树root节点右子树初始化,其素质也是一个treeNode对象
root.right.left =new(treeNode) //给二分搜刮树root节点的左子树的左侧建立一个节点
root.left.right = createTreeNode(9)
root.reverse()
fmt.Println("********************")
root.Traverse()
//数一下二分搜刮树中元素的个数
nodeCount:=0
root.TraverseFunc(func(node *treeNode) {
nodeCount++
})
fmt.Println("********************")
fmt.Println("节点总数为:",nodeCount)
}
//运行究竟:
0
9
2
0
6
节点总数为: 5
看到没,我们后实现的TraverseFunc函数的功能非常壮大,不光仅限于遍历。