首页 > 科技 >

golang中的函数式编程

2019-10-29 06:26:23 暂无 阅读:1999 评论:0

函数式编程

本篇来进修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;

}

}

golang中的函数式编程

还有一个问题就是前面说的"正统"函数式编程要求: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:

golang中的函数式编程

然则之前的遍历函数只能实现遍历的功能,接下来让函数实现接口,那它就能干好多事了:

//函数闭包,演示二分搜刮树的遍历

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函数的功能非常壮大,不光仅限于遍历。

相关文章