高阶函数编程:探索Go语言中的函数一等公民

简介: Go 函数一等公民。你是否听说过 `Go` 语言中的函数是一等公民?如果没有,那么恭喜你,本文将带你一起揭开这个神秘的面纱。如果你已经了解这个概念,你是否知道为什么 `Go` 语言中的函数被称为一等公民?不管你的答案是什么,通过阅读本文,你将对这个概念有更深入的了解。

作者:陈明勇
个人网站:https://chenmingyong.cn
文章持续更新,如果本文能让您有所收获,欢迎关注本号。
微信阅读可搜《Go 技术干货》。这篇文章已被收录于 GitHub https://github.com/chenmingyong0423/blog 欢迎大家 Star 催更并持续关注。

前言

你是否听说过 Go 语言中的函数是一等公民?如果没有,那么恭喜你,本文将带你一起揭开这个神秘的面纱。如果你已经了解这个概念,你是否知道为什么 Go 语言中的函数被称为一等公民?不管你的答案是什么,通过阅读本文,你将对这个概念有更深入的了解。

准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。

一等公民

In a given programming language design, a first-class citizen is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, and assigned to a variable.

什么是一等公民?上面引文来自维基百科,直译过来的意思是:“在给定的编程语言设计中,一等公民是指支持所有通常可用于其他实体的操作的实体。这些操作通常包括作为参数传递、从函数返回和赋值给变量。”。

直译过来似乎有点难以理解,没关系,我们可以用更简单的方式来理解:在编程语言设计中,被称为一等公民的元素可以自由地进行常见的操作,如作为参数传递、从函数返回和赋值给变量。

Go 语言中,函数具备这些特性,可以赋值给变量、作为参数传递,并且可以作为函数的返回值。
func 一等公民.jpg

函数作为一等公民的实际运用示例

当我们理解了 Go 语言中的函数为什么被视为一等公民之后,让我们来探索一下它作为一等公民的实际运用吧。

赋值给变量

Go 语言中,函数是一种类型,它可以像其他类型(如 int64string 等)一样被赋值给变量,这意味着我们可以创建一个变量,将函数赋值给它,然后通过该变量来调用函数。

将普通函数赋值给变量

我们可以将普通函数赋值给变量,以便通过变量调用该函数。下面是一个示例代码:

import (
    "fmt"
)

func SayHello(s string) {
   
   
    fmt.Println(s)
}

func main() {
   
   
    sayHelloFunc := SayHello
    sayHelloFunc("你好,我是陈明勇") // 你好,我是陈明勇
}

在上面的例子中,首先我们定义了一个普通函数 SayHello(s string),该函数接受一个字符串参数 s,并在函数体中使用 fmt.Println 函数打印字符串;

然后在 main 函数中,我们将该函数赋值给变量 sayHelloFunc,通过这个变量,我们可以调用 SayHello 函数,实现相同的功能。这种方式可以在需要动态选择函数的情况下使用,使得代码更加 灵活可复用

创建匿名函数并赋值给变量

除了将普通函数赋值给变量,我们还可以通过创建匿名函数的形式并将其赋值给变量。下面是一个示例代码:

import (
    "fmt"
)

func main() {
   
   
    sayHelloFunc := func(s string) {
   
   
        fmt.Println(s)
    }
    sayHelloFunc("你好,我是陈明勇") // 你好,我是陈明勇
}

在上述代码中,我们使用 func 关键字创建了一个匿名函数,该函数也是接受一个字符串参数 s,并在函数体中使用 fmt.Println 函数打印字符串;然后,我们将该匿名函数赋值给 sayHelloFunc 变量。通过 sayHelloFunc 变量,我们可以调用匿名函数并传入相应的参数,实现相同的功能。

匿名函数的创建方式灵活且简洁,特别适用于一次性的函数需求或需要在不同的上下文中定义函数的场景。

作为参数传递

Go 语言中,函数可以作为函数参数传递给其他函数,这使得函数可以更加灵活的操作和组合。我们来看看一个时间转换的例子;

import (
    "fmt"
    "time"
)

// ApplyFormatTimeToStringFunc 根据参数 t 和 operation,将时间类型转成对应格式的字符串类型,字符串的格式由参数 operation 决定
// 如果参数 t 为零值,则返回空字符串
func ApplyFormatTimeToStringFunc(t time.Time, operation func(t time.Time) string) string {
   
   
    if t.IsZero() {
   
   
        return ""
    }
    return operation(t)
}

// FormatTimeToString 将时间转成 yyyy-MM-dd 的形式
func FormatTimeToString(t time.Time) string {
   
   
    return t.Format(time.DateOnly)
}

// FormatDateTimeToString 将时间转成 yyyy-MM-dd HH:mm:ss 的形式
func FormatDateTimeToString(t time.Time) string {
   
   
    return t.Format(time.DateTime)
}

func main() {
   
   
    // yyyy-MM-dd
    formatTimeToString := ApplyFormatTimeToStringFunc(time.Now(), FormatTimeToString)
    fmt.Println(formatTimeToString) // 2023-07-18

    // yyyy-MM-dd HH:mm:ss
    formatDateTimeToString := ApplyFormatTimeToStringFunc(time.Now(), FormatDateTimeToString)
    fmt.Println(formatDateTimeToString) // 2023-07-18 00:00:00
}

在上述例子中,首先我们定义了一个 ApplyFormatTimeToStringFunc 函数,该函数接收一个时间类型参数 t 和一个函数类型参数 operation,根据参数 toperation,将时间类型转成字符串类型,字符串的格式由参数 operation 决定;

然后定义两个操作函数 FormatTimeToStringFormatDateTimeToString,这两个函数分别将时间转换为 yyyy-MM-ddyyyy-MM-dd HH:mm:ss 的格式;

最后在 main 函数中,我们通过将不同的操作函数作为参数传递给 ApplyFormatTimeToStringFunc 函数来格式化当前时间。通过使用函数作为参数传递给另一个函数,动态改变函数的行为,使得我们可以根据需要选择不同的格式化方式来处理时间,提高代码的灵活性和可复用性。

作为函数的返回值

Go 语言中,函数除了可以赋值给变量和作为参数进行传递以外,它还可以作为函数的返回值进行使用。以下是示例代码:

import (
    "fmt"
)

func CreateDialogueFormatter(name string) func(string) string {
   
   
    return func(s string) string {
   
   
        return fmt.Sprintf("[%s]: ", name) + s
    }
}

func main() {
   
   
    DialogueOfCmy := CreateDialogueFormatter("陈明勇")
    fmt.Println(DialogueOfCmy("你好")) // [陈明勇]: 你好

    DialogueOfGopher := CreateDialogueFormatter("Gopher")
    fmt.Println(DialogueOfGopher("你好")) // [Gopher]: 你好
}

在上面的例子中,首先我们定义了 CreateDialogueFormatter 函数,该函数接收一个 name 参数,用于设置对话人昵称,并返回一个可定制化的对话函数;

然后在 main 函数中,通过调用 CreateDialogueFormatter 函数并传入不同的昵称,可以创建多个针对不同对话人的对话函数。

通过将函数作为返回值,我们可以在运行时动态地生成函数,从而使函数更具灵活性和可定制性。

小结

函数作为一等公民在 Go 语言中非常重要,借助其三大特性,我们能够实现高阶函数编程,提升代码的灵活性和可复用性。

目录
相关文章
|
5天前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
53 22
零值在go语言和初始化数据
|
7天前
|
安全 算法 程序员
在go语言中使用泛型和反射
【7月更文挑战第8天】本文介绍go支持泛型后,提升了代码复用,如操作切片、映射、通道的函数,以及自定义数据结构。 泛型适用于通用数据结构和函数,减少接口使用和类型断言。
68 1
在go语言中使用泛型和反射
|
9天前
|
缓存 编译器 Shell
回顾go语言基础中一些特别的概念
【7月更文挑战第6天】本文介绍Go语言基础涵盖包声明、导入、函数、变量、语句和表达式以及注释。零值可用类型如切片、互斥锁和缓冲,支持预分配容量以优化性能。
40 2
回顾go语言基础中一些特别的概念
|
13天前
|
存储 Go API
一个go语言编码的例子
【7月更文挑战第2天】本文介绍Go语言使用Unicode字符集和UTF-8编码。Go中,`unicode/utf8`包处理编码转换,如`EncodeRune`和`DecodeRune`。`golang.org/x/text`库支持更多编码转换,如GBK到UTF-8。编码规则覆盖7位至21位的不同长度码点。
115 1
一个go语言编码的例子
|
16天前
|
JSON 算法 测试技术
在go语言中调试程序
【6月更文挑战第29天】Go语言内置`testing`包支持单元测试、基准测试和模糊测试。`go test`命令可执行测试,如`-run`选择特定测试,`-bench`运行基准测试,`-fuzz`进行模糊测试。
37 2
在go语言中调试程序
|
5天前
|
JSON Java Go
Go 语言性能优化技巧
在Go语言中优化性能涉及数字字符串转换(如用`strconv.Itoa()`代替`fmt.Sprintf()`)、避免不必要的字符串到字节切片转换、预分配切片容量、使用`strings.Builder`拼接、有效利用并发(`goroutine`和`sync.WaitGroup`)、减少内存分配、对象重用(`sync.Pool`)、无锁编程、I/O缓冲、正则预编译和选择高效的序列化方法。这些策略能显著提升代码执行效率和系统资源利用率。
42 13
|
1天前
|
Cloud Native Java Go
为什么要学习Go语言?
GO logo的核心理念,即简单胜于复杂。使用现代斜体无衬线字体与三条简单的运动线相结合,形成一个类似于快速运动的两个轮子的标记,传达速度和效率。字母的圆形暗示了GO地鼠的眼睛,创造了一个熟悉的形状,让标记和吉祥物很好地搭配在一起。
12 4
|
5天前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
41 8
|
7天前
|
前端开发 JavaScript Go
|
5天前
|
存储 Go
go语言中fmt格式化包和内置函数汇总
【7月更文挑战第10天】本文介绍fmt包和`Errorf`用于创建格式化的错误消息。`fmt`包还涉及一些接口,如`Formatter`、`GoStringer`、`ScanState`、`Scanner`和`Stringer`,支持自定义格式化和输入/输出处理。
17 1