再谈函数式编程:释放编程创造力
当抽象程度足够高,编程就能接近数学的优雅。
在“Go 模板:用代码生成代码”一文中,谈到了生成器模式的实现。 先 Copy 如下:
生成器模式(Builder)
假设我们要造一辆车,车有车身、引擎、座位、轮子。Go 的生成器模式的代码是这样子的:
package model
import "fmt"
type ChinaCar struct {
Body string
Engine string
Seats []string
Wheels []string
}
func newChinaCar(body string, engine string, seats []string, wheels []string) *ChinaCar {
return &ChinaCar{
Body: body,
Engine: engine,
Seats: seats,
Wheels: wheels,
}
}
type CarBuilder struct {
body string
engine string
seats []string
wheels []string
}
func ChinaCharBuilder() *CarBuilder {
return &CarBuilder{}
}
func (b *CarBuilder) Build() *ChinaCar {
return newChinaCar(b.body, b.engine, b.seats, b.wheels)
}
func (b *CarBuilder) Body(body string) *CarBuilder {
b.body = body
return b
}
func (b *CarBuilder) Engine(engine string) *CarBuilder {
b.engine = engine
return b
}
func (b *CarBuilder) Seats(seats []string) *CarBuilder {
b.seats = seats
return b
}
func (b *CarBuilder) Wheels(wheels []string) *CarBuilder {
b.wheels = wheels
return b
}
func main() {
car := ChinaCharBuilder().
Body("More advanced").
Engine("Progressed").
Seats([]string{"good", "nice"}).
Wheels([]string{"solid", "smooth"}).
Build()
fmt.Printf("%+v", car)
}
生成器模式怎么写?遵循三步即可:
(1) 先构造一个对应的生成器,这个生成器与目标对象有一样的属性;
(2) 对于每一个属性,有一个方法设置属性,然后返回生成器的引用本身;
(3) 最后调用生成器的 Build 方法,这个方法会调用目标对象的构造器来生成目标对象。
选项器模式(Optional)
与生成器模式类似,还有一种,称之为“选项器模式”。代码如下:
看上去是不是结构和生成器模式很像?但两者的用途完全不同:
- 生成器模式用于构建由多个子部件共同组成的复杂整体;子部件可能有紧密的交互关系。
- 选项器模式灵活组合多个选项。选项之间没有交互关系。
type ElementOperationResultQuery struct {
AgentId string
ElementId string
ElementIds []string
}
type Option func(c *ElementOperationResultQuery)
type ElementOperationResultQueryOption struct {
Opts ElementOperationResultQuery
}
func AgentId(agentId string) Option {
return func(opts *ElementOperationResultQuery) {
opts.AgentId = agentId
}
}
func ElementId(elementId string) Option {
return func(opts *ElementOperationResultQuery) {
opts.ElementId = elementId
}
}
func ElementIds(elementIds []string) Option {
return func(opts *ElementOperationResultQuery) {
opts.ElementIds = elementIds
}
}
func NewElementOperationResultQuery(opts ...Option) *ElementOperationResultQuery {
elementOperationResultQuery := ElementOperationResultQuery{}
for _, opt := range opts {
// 函数指针的赋值调用
opt(&elementOperationResultQuery)
}
return &elementOperationResultQuery
}
func main() {
query := NewElementOperationResultQuery(AgentId("abc"), ElementId("bcd"))
fmt.Println(query)
}
流水线模式(Pipeline)
对选项器模式稍加改造,就可以发现其中蕴藏的 Pipeline 模式。
将上面的 ElementOperationResultQuery 里的数据换成列表或 Context 对象,将函数换成数据处理函数,就变成了如下模式:
package main
import (
"fmt"
"sort"
)
type Data struct {
List []int
}
type SubRoutine func(c *Data) *Data
func Add(i int) SubRoutine {
return func(opts *Data) *Data {
for k, _ := range opts.List {
opts.List[k] = opts.List[k] + i
}
return opts
}
}
func Multi(j int) SubRoutine {
return func(opts *Data) *Data {
for k, _ := range opts.List {
opts.List[k] = opts.List[k] * j
}
return opts
}
}
func Sort() SubRoutine {
return func(opts *Data) *Data {
sort.Ints(opts.List)
return opts
}
}
func Pipeline(data Data, opts ...SubRoutine) *Data {
var result = &data
for _, opt := range opts {
// 函数指针的赋值调用
result = opt(result)
}
return result
}
func main() {
data := Data{[]int{2, 5, 7, 8, 6}}
changed := Pipeline(data, Add(1), Multi(2))
fmt.Println(*changed)
changed2 := Pipeline(data, Multi(3), Sort(), Add(2))
fmt.Println(*changed2)
}
隐隐感到:“闭包函数 + 函数式编程 + 指针”的组合,蕴藏着强大的编程表达能力。
闭包
这里讲讲闭包的神奇力量。我们知道,函数里的局部变量,在函数调用返回之后,就会销毁。但是如果函数里有一个闭包函数,这个闭包函数引用了函数里的局部变量,在外层函数返回之后,这个局部变量却不会销毁。一个利用闭包实现的简单计数器如下:
package main
import (
"fmt"
"os"
)
func count_down() func() {
i := 10
return func() {
i--
fmt.Println(i)
if i == 0 {
fmt.Println("count down to zero")
os.Exit(0)
}
}
}
func main() {
cd := count_down()
for {
cd()
}
}
函数式编程的强大威力
先温习下“函数式+泛型编程:编写简洁可复用的代码”,咱们来看看函数式编程 + 泛型能够产生怎样的表达能力。
利用闭包,很容易实现多元函数(柯里化):
package main
import (
"fmt"
"sort"
"strings"
)
func Curry[T any, S any, R any](list []T, f func(T) S) func(func([]S) R) R {
return func(ff func([]S) R) R {
ss := make([]S, 0)
for _, e := range list {
ss = append(ss, f(e))
}
return ff(ss)
}
}
type Teacher struct {
Id string
Name string
}
func main() {
teachers := []Teacher{{Id: "2003111220", Name: "fangqing"}, {Id: "2003111229", Name: "xiaoni"}}
namef := Curry[Teacher, string, string](teachers, func(t Teacher) string { return t.Name })
joinf := func(list []string) string { return strings.Join(list, ",") }
result := namef(joinf)
fmt.Println(result)
idf := Curry[Teacher, string, []string](teachers, func(t Teacher) string { return t.Id })
sortf := func(list []string) []string { sort.Strings(list); return list }
result2 := idf(sortf)
fmt.Println(result2)
}
这个 Curry 可能不太好理解。它先使用映射函数 f func(T) S 将一个 []T 转成 []S,得到一个函数。这个函数再接收另一个函数 func([]S) R,最终得到 R。
如果拆解成这两个函数的组合,可能就容易理解了:
func Convert[T any, S any](list []T, f func(T) S) []S {
ss := make([]S, 0)
for _, e := range list {
ss = append(ss, f(e))
}
return ss
}
func Collect[S any, R any](ss []S, c func([]S) R) R {
return c(ss)
}
如果这样还不太明显的话,可以将函数定义为自定义类型:
type MapFunc[T any, S any] func(t T) S
type CollectFunc[S any, R any] func([]S) R
func Curry2[T any, S any, R any](list []T, f MapFunc[T, S]) func(CollectFunc[S, R]) R {
return func(collectFunc CollectFunc[S, R]) R {
ss := make([]S, 0)
for _, e := range list {
ss = append(ss, f(e))
}
return collectFunc(ss)
}
}
idf3 := Curry2[Teacher, string, []string](teachers, func(t Teacher) string { return t.Id })
sortf3 := func(list []string) []string { sort.Strings(list); return list }
result3 := idf3(sortf3)
fmt.Println(result3)
里面那个遍历也可以进一步抽象。这个函数可以进一步抽象:
type ListMapFunc[T any, S any] func(list []T, mapFunc MapFunc[T, S]) []S
func Curry3[T any, S any, R any](list []T, listMapFunc ListMapFunc[T, S], mapFunc MapFunc[T, S]) func(CollectFunc[S, R]) R {
return func(collectFunc CollectFunc[S, R]) R {
return collectFunc(listMapFunc(list, mapFunc))
}
}
func MapList[T any, S any](list []T, f MapFunc[T, S]) []S {
ss := make([]S, 0)
for _, e := range list {
ss = append(ss, f(e))
}
return ss
}
id4 := func(t Teacher) string { return t.Id }
idf4 := Curry3[Teacher, string, []string](teachers, func(teachers []Teacher, mapFunc MapFunc[Teacher, string]) []string { return MapList(teachers, mapFunc) }, id4)
sortf4 := func(list []string) []string { sort.Strings(list); return list }
result4 := idf4(sortf4)
fmt.Println(result4)
可以看到,借助 “闭包 + 函数式编程 + 泛型+ 柯里化 + 自定义函数类型”, 可以获得了很强大的表达能力。多练习,对编程思维的提升大有裨益。
附记
用AI 能写出来么?【使用通义千问】
程序员啊,准备择日退休吧!想一想,一个普通的AI 能够在短短十秒内写出一个一流程序员才能写出的一流程序,你还挣扎什么呢?
软件开发领域就那几件事,一旦每一件事都找到 Al 的方法,再串联起来,需求自动化完成就为期不远了。
当然,程序员不会自甘退出舞台的,毕竟,他们才是最有可能掌握 AI 力量的种族。夺走你工作的不是 AI ,而是那些富有经验和直觉,思维高度活跃敏锐的善于利用 AI 力量的人。
小结
本文从生成器模式开始谈起,讲到与之相似的选项器模式,扩展成 Pipeline 模式,最后给出闭包及闭包加函数式编程组合的编程表达能力。
当你能够玩转函数式编程时,就获得了非常强大的编程表达能力。编程将再一次展示其魅力和乐趣。
函数式编程的关键在于抽象。当抽象程度足够高,编程就能接近数学的优雅。