函数

在go语言中,函数是一等公民

  • 函数本身可以作为值进行传递
  • 函数可以作为变量的值
  • 函数可以作为参数和返回值

函数式编程概念

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

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

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

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

重点说一下闭包

image-20230131175127522

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

“正统”函数式编程:

  • 不可变性:不能有状态、只有常量和函数
  • 函数只能有一个参数

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=%d\n",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方法就自动生成,所以传入的必须是reader接口类型。

改造,定义一个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("%d\n", 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

通过函数式编程,改造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更加灵活了。