高阶函数编程:探索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 语言中非常重要,借助其三大特性,我们能够实现高阶函数编程,提升代码的灵活性和可复用性。

目录
相关文章
|
7月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
7月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
1月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
1月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
2月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
2月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
205 0
|
3月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
4月前
|
设计模式 缓存 算法
Go如何进行高质量编程与性能调优实践
本文介绍了Go语言高质量编程与性能调优的实践方法。高质量编程包括良好的编码习惯(如清晰注释、命名规范)、代码风格与设计(如MVC模式)、简洁明了的代码原则,以及单元测试与代码重构的重要性。性能调优方面,涵盖算法优化、数据结构选择、I/O优化、内存管理、并行与并发处理优化及代码层面的改进。通过这些方法,可有效提升代码质量和系统性能。
94 13
|
4月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
105 10
|
4月前
|
Go 调度
GO语言函数的内部运行机制分析
以上就是Go语言中函数的内部运行机制的概述,展示了函数在Go语言编程中如何发挥作用,以及Go如何使用简洁高效的设计,使得代码更简单,更有逻辑性,更易于理解和维护。尽管这些内容深入了一些底层的概念,但我希望通过这种方式,将这些理论知识更生动、更形象地带给你,让你在理解的同时找到编程的乐趣。
68 5