go

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

ninedraft

ninedraft

Posted on June 30, 2021

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, "а"))
}

Enter fullscreen mode Exit fullscreen mode

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

С другой стороны, пачка обобщённых функций для работы с коллекциями сильно упростила бы мне работу по маппингу различных 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://go2goplay.golang.org/p/6rasgLCHxYg

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
}
Enter fullscreen mode Exit fullscreen mode

1523884876131982040


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

💖 💪 🙅 🚩
ninedraft
ninedraft

Posted on June 30, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024

How to Use KitOps with MLflow
beginners How to Use KitOps with MLflow

November 29, 2024