函数式编程概念

在函数式编程中,应用最多的就是闭包。

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。 ----维基百科

简单来说,闭包就是在函数内使用外部自由变量。

函数式编程的特点: - 函数是一等公民 - 高阶函数(函数的参数和返回值都可以是函数) - 闭包

重点介绍一下闭包:

d249c7d89ac22118f49507d72fda2103.png

可以看到,在函数体类,允许自由变量进入,而自由变量又可以与其他变量引用,形成一条链,这样的结构就是闭包。

其他语言的函数式编程

  • 在Python中闭包写法:
c4d795f41d8883f650915580643a30f4.png

可以看到,在Python中如果要使用闭包,自由变量需要进行声明。

  • C++中的闭包写法:
c075d9b1395ee12b52cd197a348f2ba3.png

C++在高级版本中,可以开始原生支持闭包

  • Java中的闭包写法:
81901da093b86e39dd38e259db583f86.png

Java只有在1.8版本可以使用function类,进行闭包操作。这也是增加的新特性,但是其实Function,并不是一个函数,它是一个类。且在使用闭包时是不能使用基础类型的,需要我们自己定义一个应用类型,才可以当做,自由变量。

GO语言中函数式编程

GO语言中因为是新诞生的语言,所以对于函数式编程进行原生支持,且函数为一等公民,下面通过几个列子来看,GO中的函数式编程。

事例1:累加器

package main

import "fmt"

func Adder()func(int)int  {
    num:=0
    return func(i int) int {
        num+=i //num为自由变量
        return num
    }
}

func main() {
    adder := Adder()
    for i := 0; i<10;i++  {
        fmt.Printf("0+...+%d=%dn",i,adder(i))
    }
}

打印:

0+...+0=0
0+...+1=1
0+...+2=3
0+...+3=6
0+...+4=10
0+...+5=15
0+...+6=21
0+...+7=28
0+...+8=36
0+...+9=45

事例2:斐波那契数列

解释一下斐波那契数列,其实就是每个数字,都是由前两个数字累加而得到。

/**
    函数生成斐波那契数列(每个数字都是由前两个数字相加得到)
 */
func fibonacci() func () int {
    a, b := 0, 1
    return func() int {
        //在这里,生成关键
        // 1 1 2 3 5 8
        //   a b
        //     a b
        a, b = b, a+b
        return a
    }
}
func main() {
    fun := fibonacci()
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
    fmt.Println(fun())
}

打印:

1
1
2
3
5
8
13
21
34
55

改造斐波那契数列函数

我们希望像读文件一样来,让其自动生成。所以就需要实现read接口:

将之前的打印函数拿过来:

func printContentFile(reader io.Reader) {
    scanner := bufio.NewScanner(reader)
    for scanner.Scan() {
        println(scanner.Text())
    }
}
printContentFile

改造,定义一个type:函数。使其实现Reader接口。

//函数实现接口
//定义一个函数,使用type修饰。可以实现接口,也就是说只要是被type修饰的东西都可以实现接口。
type IntGen func() int
//实现read接口
func (g IntGen) Read(p []byte) (n int, err error) {
    next := g()

    if next > 10000 { //这里因为不设置退出会一直打印下去,所以做了限制
        return 0, io.EOF
    }
    s := fmt.Sprintf("%dn", next)
    return strings.NewReader(s).Read(p)
}

函数返回为刚定义的函数。

/**
    函数生成斐波那契数列(每个数字都是由前两个数字相加得到)
 */
func fibonacci() IntGen {
    a, b := 0, 1
    return func() int {
        //在这里,生成关键
        // 1 1 2 3 5 8
        //   a b
        //     a b
        a, b = b, a+b
        return a
    }
}

调用:

func main() {

    fun := fibonacci()

    printContentFile(fun)
}

打印:

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765

通过这个例子我们发现,在go语言中,函数也可以实现接口。也可以理解了函数为‌一等公民概念。

事例3:修改tree.Node

Traverse
//通过函数式编程,使traverse更具有扩展性
func (node *Node)FunTraverse(f func(node *Node)) {//传入一个函数作为参数
    if node == nil {
        return
    }
    node.Left.FunTraverse(f)
    f(node)
    node.Right.FunTraverse(f)
}

调用:

func main() {
    root := tree.Node{Value: 5}
    root.Left = &tree.Node{Value: 1}
    root.Right = &tree.Node{Value: 2}
    root.Left.Right= &tree.Node{Value: 3}
    root.Right.Left = &tree.Node{Value: 4}
    //正常调用
    root.Traverse()

  //函数式方法 打印信息
    root.FunTraverse(func(node *tree.Node) {
        node.Print()
    })
    fmt.Println("-----------")
    //函数式方法 计数
    rootCount := 0
    root.FunTraverse(func(node *tree.Node) {
        rootCount++
    })
}

打印:

1 3 5 4 2 
-----------
1 3 5 4 2 
Traverse counts : 5
Traverse

作者所有的学习源码在 go学习源码github地址,如果觉得有用的话帮小智贡献一个star