首页 > 编程语言 >再谈函数式编程:释放编程创造力

再谈函数式编程:释放编程创造力

时间:2024-01-28 10:55:36浏览次数:27  
标签:创造力 return string 编程 list 再谈 func any opts

当抽象程度足够高,编程就能接近数学的优雅。

在“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 模式,最后给出闭包及闭包加函数式编程组合的编程表达能力。

当你能够玩转函数式编程时,就获得了非常强大的编程表达能力。编程将再一次展示其魅力和乐趣。

函数式编程的关键在于抽象。当抽象程度足够高,编程就能接近数学的优雅。

参考资料

标签:创造力,return,string,编程,list,再谈,func,any,opts
From: https://www.cnblogs.com/lovesqcc/p/17992558

相关文章

  • Julia编程基础
    本文介绍了Julia这一主要面向自然科学的编程语言的基本安装与使用,建议读者最好在熟练使用Python的前提下再阅读本文。如果是对Python的语法非常熟悉的人,应该很容易看懂本文并初步掌握Julia的基本语法和使用逻辑。Julia最吸引人的地方在于他简单如Python的语法,但兼具了C++......
  • CSAPP学习笔记——Chapter10,11 系统级I/O与网络编程
    CSAPP学习笔记——Chapter10,11系统级I/O与网络编程Chapter10系统级I/O系统级I/O这一章的内容,主要可以通过这张图概括:UnixI/O模型是在操作系统内核中实现的。应用程序可以通过诸如open、close、lseek、read、write和stat这样的函数来访UnixI/O。较高级别的RIO和标......
  • Windows内核开发-[4]、内核编程基础(1)
    在前面的文章中,介绍了如何配置开发环境以及如何进行调试。接下来的几篇文章,将会重点介绍内核编程中所需要了解的一些理论基础。我写这个系列文章的主要目的是方便以后自己查阅,同时也给正在学习内核开发的小伙伴一些参考,所以我会尽可能地以最简单的方式进行描述。如果在阅读过程......
  • C# 面向对象编程进阶:构造函数详解与访问修饰符应用
    C#构造函数构造函数是一种特殊的方法,用于初始化对象。构造函数的优势在于,在创建类的对象时调用它。它可以用于为字段设置初始值:示例获取您自己的C#服务器创建一个构造函数://创建一个Car类classCar{publicstringmodel;//创建一个字段//为Car类创建一个......
  • C# 面向对象编程进阶:构造函数详解与访问修饰符应用
    C#构造函数构造函数是一种特殊的方法,用于初始化对象。构造函数的优势在于,在创建类的对象时调用它。它可以用于为字段设置初始值:示例获取您自己的C#服务器创建一个构造函数://创建一个Car类classCar{publicstringmodel;//创建一个字段//为Car类创建一......
  • .net 高并发(一,异步编程模型)
    在.NET中,异步编程模型(Async/Await)是一种处理高并发的好方法。它允许开发人员以非阻塞的方式编写异步代码,从而使应用程序能够同时处理多个请求或任务,从而提高并发性能。下面是使用Async/Await进行异步编程的一般步骤:定义一个返回Task或Task<TResult>的方法,并在方法签名中使用as......
  • Python并发编程之锁
    锁【一】同步原语操作系统—同步原语-CSDN博客实现互斥锁的并发程序设计-皮特森算法【Peterson算法-维基百科】​ 同步原语是一组用于协调多个执行线程或进程之间操作顺序和共享资源访问的基本机制。这些机制的目的是确保多个执行单元能够按照某种协调方式执行,以避免并发......
  • 初识C语言:掌握未来的编程利器
    ​✨✨欢迎大家来到贝蒂大讲堂✨✨​......
  • Python并发编程之进程池,线程池与信号量
    【一】进程池和线程池【0】池池的概念:资源管理:池用于管理和维护一组资源(如进程或线程),而不是每次需要时都创建和销毁这些资源。这有助于减少创建和销毁的开销。并发处理:池允许并发地执行多个任务,每个任务由池中的一个资源处理。这提高了程序的并发性能。任务队列:池通......
  • SSAS 利用Adomd 编程实现Cube的自定义配置
    利用Adomd执行Cube数据库的配置。 需要下载:Microsoft.AnalysisServices.AdomdClient,安装,再搜索到DLL的位置,在程序里引用下载地址 环境:SqlServer2022表格模型脚本内容存放在:s01.json文件里AdomdConnectionconn=newAdomdConnection("DataSourc......