Go语言函数式编程实践指南
Go语言函数式编程实践指南 尝试用Go进行函数式编程,下面的代码对我来说运行良好,但希望与社区分享以获取审查、评论和反馈。
package main
import (
"fmt"
"log"
"sort"
"time"
)
const (
layoutISO = "2006-01-02"
layoutUS = "January 2, 2006"
custom = "1/2/2006"
)
type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
Warehouse string
Item string
Batches Lots
}
type Lots []Lot
type Lot struct {
Date time.Time
Key string
Value float64
}
func main() {
fmt.Println("Hello, 世界")
date := "12/31/19" // "1999-12-31"
t, _ := time.Parse(custom, date)
var inventories = new(Inventories)
var inventory = new(Inventory) // Or = Inventory{} both are working //warehouse[item[batch, qty]]
inventory.Warehouse = "DMM"
inventory.Item = "Helmet"
inventory.Batches = append(inventory.Batches, Lot{t, "Jan", 10})
inventory.InsertBatch(Lot{time.Now(), "Jan", 30})
inventory.Batches.Insert(Lot{time.Now(), "Feb", 30})
fmt.Printf("\nBefore grouping: %v %T\n", inventory, inventory)
x := Inventory{
Warehouse: "DMM",
Item: "Gloves",
Batches: Lots{
Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 20},
Lot{mustTime(time.Parse(custom, "1/5/2020")), "Jan", 40},
Lot{mustTime(time.Parse(custom, "2/9/2020")), "Feb", 60},
},
}
fmt.Printf("\nBefore grouping: %v %T\n", x, x)
// Below can be used for grouping batches in each warehouse seperatly, this can be combined in the lines under
// inventory.Batches.Group()
// x.GroupBatches()
// fmt.Printf("\nAfter grouping: %v %T\n", inventory, inventory)
// fmt.Printf("\nAfter grouping: %v %T\n", x, x)
// Above can be replaced by below
inventories.Insert(*inventory)
inventories.Insert(x)
inventories.GroupBatches()
fmt.Printf("\nInventories after gouping batches: %v %T\n", inventories, inventories)
inventories.SortBatches()
fmt.Printf("\nInventories after sorting batches: %v %T\n", inventories, inventories)
}
func (i *Inventories) Insert(x Inventory) {
*i = append(*i, x)
}
func (i *Inventories) GroupBatches() {
inv := new(Inventories)
for _, el := range *i {
el.GroupBatches()
inv.Insert(el)
}
(*i).ReplaceBy(inv)
}
func (i *Inventories) SortBatches() {
inv := new(Inventories)
for _, el := range *i {
sort.Sort(Lots(el.Batches))
inv.Insert(el)
}
(*i).ReplaceBy(inv)
}
func (i *Inventories) ReplaceBy(x *Inventories) {
*i = *x
}
func (i *Inventory) InsertBatch(x Lot) {
(*i).Batches = append((*i).Batches, x)
}
func (i *Inventory) GroupBatches() {
(*i).Batches.Group()
}
func (p *Lots) Group() {
lots := new(Lots)
lots.FromMap(p.Map())
p.ReplaceBy(lots)
}
func (p *Lots) FromMap(m map[string]float64) {
for k, v := range m {
(*p).Insert(Lot{time.Now(), k, v})
}
}
// Below to enable sorting: sort.Sort(Lots(lots))
func (l Lots) Len() int { return len(l) }
func (l Lots) Less(i, j int) bool { return (l[i].Date).Before(l[j].Date) } // { return l[i].Key < l[j].Key }
func (l Lots) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (p *Lots) Insert(x Lot) {
*p = append(*p, x)
}
func (p *Lots) ReplaceBy(x *Lots) {
*p = *x
}
func (p *Lots) Map() map[string]float64 {
sum := make(map[string]float64)
for _, el := range *p {
sum[el.Key] = sum[el.Key] + el.Value
}
return sum
}
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
func mustTime(t time.Time, err error) time.Time {
failOnError(err)
return t
}
func failOnError(err error) {
if err != nil {
log.Fatal("Error:", err)
panic(err)
}
}
输出是:
[Running] go run "d:\goplay\grouping.go"
Hello, 世界
Before grouping: &{DMM Helmet [{0001-01-01 00:00:00 +0000 UTC Jan 10} {2020-06-05 00:26:16.9165066 +0300 +03 m=+0.006981101 Jan 30} {2020-06-05 00:26:16.9165066 +0300 +03 m=+0.006981101 Feb 30}]} *main.Inventory
Before grouping: {DMM Gloves [{2020-01-07 00:00:00 +0000 UTC Jan 50} {2020-02-01 00:00:00 +0000 UTC Feb 20} {2020-01-05 00:00:00 +0000 UTC Jan 40} {2020-02-09 00:00:00 +0000 UTC Feb 60}]} main.Inventory
Inventories after gouping batches: &[{DMM Helmet [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 40} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 30}]} {DMM Gloves [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 90} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 80}]}] *main.Inventories
Inventories after sorting batches: &[{DMM Helmet [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 40} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 30}]} {DMM Gloves [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 90} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 80}]}] *main.Inventories
[Done] exited with code=0 in 3.624 seconds
5 回复
你能解释一下这为什么是函数式编程吗?据我所知,函数式编程的一个重要方面是能够将一个 func 作为另一个 func 的参数来使用。
以下是我之前写的一些示例,可能会有所帮助。V1 是所有版本中最简单的。
V1: https://play.golang.org/p/xBgJUlz2uYX V2: https://play.golang.org/p/1_NXGwTtBHJ V3: https://play.golang.org/p/gHRO16rEEw7 V4: https://play.golang.org/p/2gdsQyvL0yi
根据维基百科的定义,
函数式编程是一种编程范式——一种构建计算机程序结构和元素的风格——它将计算视为数学函数的求值,并避免改变状态和可变数据。
因此,在函数式编程中,有两个非常重要的规则:
- 无数据突变:这意味着数据对象在创建后不应被更改。
- 无隐式状态:应避免隐藏/隐式状态。在函数式编程中,状态并未被消除,而是使其可见和显式。
您提到的只是函数式编程的概念之一,即高阶函数,它允许您:
- 将函数赋值给变量,
- 将函数作为参数传递给另一个函数,
- 从一个函数返回另一个函数。
以下是一个使用 Go 语言的示例:
package main
import "fmt"
func main() {
var list = []string{"Orange", "Apple", "Banana", "Grape"}
// 我们将数组和一个函数作为参数传递给 mapForEach 方法。
var out = mapForEach(list, iter)
fmt.Println(out) // [6, 5, 6, 5]
}
func iter(x string) int {
return len(x)
}
// 高阶函数接受一个数组和一个函数作为参数
func mapForEach(arr []string, fn func(it string) int) []int {
var newArray = []int{}
for _, r := range arr {
// 我们执行传入的方法 // 在这个例子中是 func iter
newArray = append(newArray, fn(r))
}
return newArray
}

这是一个很好的函数式编程实践示例,展示了Go语言中如何通过方法组合和不可变操作来实现函数式风格。以下是一些专业评论:
代码优点
- 不可变操作:
Group()和SortBatches()等方法返回新对象而不是修改原对象,符合函数式编程原则 - 方法链式调用:通过方法组合实现数据转换流水线
- 类型安全:使用具体类型而非
interface{},保持类型安全
可改进的函数式编程实践
1. 使用高阶函数
// 添加Map和Filter高阶函数
func (p Lots) Map(f func(Lot) Lot) Lots {
result := make(Lots, len(p))
for i, lot := range p {
result[i] = f(lot)
}
return result
}
func (p Lots) Filter(f func(Lot) bool) Lots {
result := Lots{}
for _, lot := range p {
if f(lot) {
result = append(result, lot)
}
}
return result
}
// 使用示例
filtered := lots.Filter(func(l Lot) bool {
return l.Value > 20
})
mapped := lots.Map(func(l Lot) Lot {
return Lot{l.Date, l.Key, l.Value * 1.1}
})
2. 纯函数重构
// 将Group改为纯函数
func GroupLots(lots Lots) Lots {
grouped := make(map[string]float64)
for _, lot := range lots {
grouped[lot.Key] += lot.Value
}
result := Lots{}
for key, value := range grouped {
result = append(result, Lot{time.Now(), key, value})
}
return result
}
// 使用函数组合
func ProcessInventories(inventories Inventories) Inventories {
return inventories.Map(func(inv Inventory) Inventory {
return Inventory{
Warehouse: inv.Warehouse,
Item: inv.Item,
Batches: GroupLots(inv.Batches).Sort(),
}
})
}
3. 添加Reduce操作
func (p Lots) Reduce(initial float64, f func(float64, Lot) float64) float64 {
result := initial
for _, lot := range p {
result = f(result, lot)
}
return result
}
// 使用示例
totalValue := lots.Reduce(0, func(acc float64, lot Lot) float64 {
return acc + lot.Value
})
4. 简化现有代码
// 当前Group方法可以更函数式
func (p Lots) Group() Lots {
return p.ReduceByKey(
func(lot Lot) string { return lot.Key },
func(acc, lot Lot) Lot {
return Lot{lot.Date, lot.Key, acc.Value + lot.Value}
},
)
}
func (p Lots) ReduceByKey(
keyFunc func(Lot) string,
reduceFunc func(Lot, Lot) Lot,
) Lots {
groups := make(map[string]Lot)
for _, lot := range p {
key := keyFunc(lot)
if existing, found := groups[key]; found {
groups[key] = reduceFunc(existing, lot)
} else {
groups[key] = lot
}
}
result := make(Lots, 0, len(groups))
for _, lot := range groups {
result = append(result, lot)
}
return result
}
性能考虑
当前实现中多次创建新切片,对于大数据集可能影响性能。可以考虑:
// 使用builder模式优化
type LotsBuilder struct {
lots Lots
}
func NewLotsBuilder() *LotsBuilder {
return &LotsBuilder{make(Lots, 0)}
}
func (b *LotsBuilder) Add(lot Lot) *LotsBuilder {
b.lots = append(b.lots, lot)
return b
}
func (b *LotsBuilder) Build() Lots {
return b.lots
}
测试友好性
纯函数更容易测试:
func TestGroupLots(t *testing.T) {
lots := Lots{
Lot{Date: time.Now(), Key: "Jan", Value: 10},
Lot{Date: time.Now(), Key: "Jan", Value: 20},
Lot{Date: time.Now(), Key: "Feb", Value: 30},
}
grouped := lots.Group()
if len(grouped) != 2 {
t.Errorf("Expected 2 groups, got %d", len(grouped))
}
}
总体而言,这是一个很好的起点。通过添加更多高阶函数和保持操作不可变性,可以进一步强化函数式编程风格。


