Go, функциональное программирование

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

Я не думаю, что в обозримом будущем даже после появления type params в Go станет популярным функциональный подход к работе с коллекциями. Без итераторов в стандартной библиотеке, параметризированных методов, компактного синтаксиса лямбд и каррирования попытки натянуть map, filter или fold на 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.

В целом эта штука считается линтерами вредной, да и никем толком не используется. Но вот кажется мне, что с появлением дженериков она получит второе дыхание:

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
}

15