Go, функциональное программирование 
Tags: go
Published: 2021-06-30 20:47:43

А помните вот эти вот все библиотеки для функционального Go, с монадами и левыми свёртками? А ведь в какой-то момент их было пилить популярнее, чем роутеры.

Я не думаю, что в обозримом будущем даже после появления type params в Go станет популярным функциональный подход к работе с коллекциями. Без итераторов в стандартной библиотеке, [параметризированных методов](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#methods-may-not-take-additional-type-arguments), компактного синтаксиса лямбд и каррирования попытки натянуть `map`, `filter`  или  `fold` на Go наткнутся на непропорцинально возросшее количество визуального шума.

Сравним два подхода подсчёта букв "а" в очищенных именах котиков
```go
var cats = []string{"thusМамона", "thusАсмодей", "thusВельзевул"}

// функциональный
// никакого вам чейнинга!
var stripped = Map(cats, func(name string) string {
    return strings.TrimPrefix(name, "thus")
})
var lowered = Map(stripped, strings.ToLower)
var counts = Map(counts, func(name string) int {
    return strings.Count(name, "а")
})

// императивный
var counts = make([]int, 0, len(cats))
for _, name := range cats {
    var stripped = strings.TrimPrefix(name, "thus")
    var lowered = strings.ToLower(stripped)
    counts = append(counts, strings.Count(name, "а"))
}

```

Для меня второй вариант кода выглядит несколько чище (я даже не поднимаю вопрос производительности). И думаю, разница станет ещё заметнее, если принести сюда обработку ошибок.

С другой стороны, пачка обобщённых функций для работы с коллекциями сильно упростила бы мне работу по маппингу различных DTO друг в друга. Типичная ситуация: есть слайс структур `User{ID, Name, Labels}`, и для разных нужд из слайса нужно выудить срез по каждому из полей. Вот и пишешь кучу функций вида `usersIDs`, `usersNames` и `usersLables` там, где можно было бы обойтись двумя строками дженерик кода. Опять же, только с хэш-мапами мне приходит с десяток функций вида `func[K, V comparable] Reverse(map[K]V) map[V]K`, которые вроде бы и просто писать самому, но так надоедает!

Ещё можно вспомнить, что есть такой экзотический механизм в go - method expressions. Это что-то вроде обратного каррирования методов - если у типа   `Teapot` есть метод `.Brew(water) error`, то выражение `Teapot.Brew` будет иметь тип `func(Teapot, water) error`. 

В целом эта штука считается [линтерами вредной](https://go-critic.github.io/overview.html#methodexprcall), да и никем толком не используется. Но вот кажется мне, что с появлением дженериков она получит второе дыхание:

<https://go2goplay.golang.org/p/6rasgLCHxYg>
```go
func main() {
	var cats = []Cat{
		{100},
		{500},
		{420},
	}

	fmt.Printf("%q\n", Map(cats, Cat.Mew))
    // ["mew 100" "mew 500" "mew 420"]
}

type Cat struct{ index int }

func (cat Cat) Mew() string {
	return fmt.Sprintf("mew %d", cat.index)
}

func Map[X, Y any](xx []X, fn func(X) Y) []Y {
	var yy = make([]Y, 0, len(xx))
	for _, x := range xx {
		yy = append(yy, fn(x))
	}
	return yy
}
```
![1523884876131982040](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6sccyk4kttmvky87ic0.jpg)

---
КПДВ: <https://github.com/egonelbre/gophers>