Еволюція в Go: огляд атавізмів

Привіт. Go — це найкраща мова програмування, на мою думку. До того ж високооплачувана. Звісно, такою вона стала завдяки регулярним покращенням. Їх і розглянемо в цій розважально-пізнавальній статті.

Передмова

Якщо ви тільки почали працювати з Go на проєкті, який до вас розробляли багато років, ви зустрінете дивні конструкції. Так розробники намагались замаскувати тодішні проблеми цієї мови програмування.

Їх і розглянемо далі, а також версії Go, в яких вони стали атавізмами.

Робота з мапами

Коли я працював з PHP, там були всі можливі функції для роботи з асоціативними масивами: array_keys, array_values та ще майже сотня інших: php.net/manual/en/ref.array.php.

Відповідно для отримання ключів мапи в PHP є функція array_keys, в JavaScript — Object.keys, а в Go потрібно було кожен раз писати рутинний код, який виглядав так:

package main

import (
	"fmt"
)

func main() {
    // https://survey.stackoverflow.co/2024/
    var keyTerritoryPlaceMap = map[string]int{
        "USA":         1,
        "Germany":     2,
        "India":       3,
        "UK":          4,
        "Ukraine":     5,
        "Canada":      6,
        "France":      7,
        "Poland":      8,
        "Netherlands": 9,
        "Brazil":      10,
    }

    // Atavism
    keys := make([]string, 0, len(keyTerritoryPlaceMap))
    for key := range keyTerritoryPlaceMap {
        keys = append(keys, key)
    }

    fmt.Println(keys)
}

Після появи дженериків у версії 1.18 стала доступною функція maps.Keys:

package main

import (
	"fmt"

	"golang.org/x/exp/maps"
)

func main() {
    // ...

    // Modern
    keys := maps.Keys(keyTerritoryPlaceMap)

    fmt.Println(keys)
}

Реалізація функції maps.Keys з пакету golang.org/x/exp/maps:

package maps

// Keys returns the keys of the map m.
// The keys will be in an indeterminate order.
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
	r := make([]K, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}

Робота з max та min

У більшості скриптових мов програмування є функції max та min, в Go ці функції також доступні у пакеті math, але вони працюють тільки з числами типу float64. Тому зустрічав таку конструкцію:
package atavism

import (
	"fmt"
	"math"
)

func main() {
	var (
		a int64 = 1
		b int64 = 2
	)

	// Atavism
	var (
		result = int64(math.Max(float64(a), float64(b)))
	)

	fmt.Println(result)
}

У версії Go 1.21 з’явились повноцінні функції max та min:

var (
    a int64 = 1
    b int64 = 2
    c int64 = 3
)

var (
    result = max(a, b, c)
)

fmt.Println(result)

Оскільки max є вбудованою функцією, її можна використовувати у константах:

const (
    a      = 1
    b      = 2
    c      = 3
    result = max(a, b, c)
)

fmt.Println(result)
const (
    a       = "Rust"
    b       = "Go"
    c       = "Scala"
    result1 = max(a, b, c)
    result2 = max(len(a), len(b), len(c))
)

fmt.Println(result1, result2)

Цикл for range int

Цикл for став ще розумнішим у версії Go 1.22. Порівняйте самі, наскільки стало зручніше:
package main

import (
	"testing"
)

// From beginning
func BenchmarkAtavismFor(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = i
	}
}

// After Go 1.22
func BenchmarkModernFor(b *testing.B) {
	for i := range b.N {
		_ = i
	}

	for range b.N {

	}

	for range 5 {

	}
}
Я однозначно буду використовувати новий синтаксис.

Нарешті виправили помилку захоплення змінної циклу

Кожен гофер хоч раз, але зустрічав таку помилку:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var values = []string{"one", "two", "three"}
	var wg = new(sync.WaitGroup)

	wg.Add(3)
	for _, value := range values {
		go func() {
			fmt.Println(value)

			wg.Done()
		}()
	}

	wg.Wait()
}
go run ./examples/03-01-closure-for-iteration-var-err/main.go
three
three
three

Й виправляв цю помилку двома способами:

for _, value := range values {
    // Atavism
    value := value

    go func() {
        fmt.Println(value)

        wg.Done()
    }()
}
for _, value := range values {
    go func(value string) {
        fmt.Println(value)

        wg.Done()
    }(value)
}

У версії Go 1.22 цю помилку виправили, тому конструкція value := value перетворилась на атавізм.

Хоча розробники за допомогою конструкції value := value частіше виправляли іншу помилку «Захоплення змінної-вказівника циклу», яку описував у статті «50 відтінків Go по-українськи. Аналізуємо помилки» та яка також була виправлена у версії Go 1.22.

Епілог

Якщо вам сподобалась стаття, то покажіть це донатом на пластик для друку.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось22
До обраногоВ обраному4
LinkedIn

16 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Не можна стверджувати, що необхідність value:=value була помилкою. В цілому, передається змінна в замикання за значенням чи за посиланням, має регулюватись. В C++ є чітка різниця між передачою за значенням і за посиланням, там сам регулюєш.
В Python воно і зараз працює як по старому в Go, треба додавати таке ж value=value і всім з досвідом це відомо.

Коментар порушує правила спільноти і видалений модераторами.

Go lang насправді має величезну купу недоліків. Як і в цілому мови зі східними ідеологіями, де намагались не підвищувати кваліфікацію програміста — а зробити мову під програміста з низькою кваліфікацією (в данному випадку середньої і вище, бо усе таки ідеолгія була «для середнього співробітника Google» ).
Насправді вимога до кваліфікації нікуди не поділась — не з BASIC чи Visual Basic, не з Oberon, не з Java або C#, не з Python. Концептуально усеодно те саме що і FORTRAN. А от накладні розходи для контролю над пам’яттю чи синтаксичного сахару, щоб printf помилок не було залишились, як і потреби в їх обходу для збільшення перформансу. Для прикладу runc переписаний на С (один і той самий алгоритм) — crun, став показувати в 20 разів кращій перформанс по окремих операціях.
Проміж східних модернових мов особисто я поставлю Go lang на третє місце після D та Rust.
Та у Go lang величезний маркетинговий плюс — його підтримує Google, а усе що Google це модно. А там де модно — там і гроші.

Хаскель, Скалу, ОКамл та деякі інші робили для програмістів які тішать своє ЧСВ «високою кваліфікацією та розумним кодом» і в результаті маємо по чотири програміста та півтора проекти на кожну з цих мов. Потужно.

А була історія з JetBrains — коли вони казали, що з’ясували з Kotlin — що будь яка мова програмування розкручується маркетингового як і будь який інший продукт.
Як наприклад ця стаття — це в цілому присутність у ЗМІ. Тоді як бізнес Ярослава — консультації та навчання з Go lang. Напевно він би міг обрати будь яку іншу платформу задля цього, але на ній не написано Google.

Тоді як бізнес Ярослава — консультації та навчання з Go

В мене відсутній саме такий бізнес, як консультації та навчання з Go, консультації проводжу за донат

Це і є бізнес, просто соціально відповідальний :)
А в загалі в цьому є лише позитив і нічого поганого. Леніна у нас усюди звалили, та потягнули до істукану Перуна.

В мене відсутнє розуміння, як консультації за донат, які направлені на розвиток спільноти, пов’язані у вашій голові з советським катом та праслов’янським богом

Golang з хитрим Гуглом зараз з нами в одній кімнаті?

А чому хитрий ? ANTLR чи Bison усім доступні, так само як і книга Дракону як і купа книг з маркетингу та просуванню товарів на ринку і та інтернет. При прикладенні певних зусиль, зможете зробити конкуренцію.

Мова програмування — це, в першу чергу, про концепцію та підходи, а реалізація вже є другорядною. Гарний приклад цього — V (github.com/vlang/v) та Zig. Автор V намагався створити щось дуже просте і зрозуміле, але виявилося, що це не знайшло широкого застосування, бо бракувало концепції. Натомість автор Zig, який розпочав розробку пізніше за V, запропонував своє бачення управління пам’яттю та інтеграції з вже існуючими бібліотеками C, C++ та інших мов, що відразу дало результати у вигляді зростання ком’юніт та цікавих проектів як та Булочка (Bun)

Трішки доповню.

Повноцінно, а не через експериментальний пакет, функція maps.Keys стала доступною у версії Go 1.23.

В Go 1.22 додали cmp.Or.

Замість:

env := os.Getenv("ENVIRONMENT")
if env == "" {
    env = "prod"
}

можна робити

env := cmp.Or(os.Getenv("ENVIRONMENT"), "prod")

В Go 1.22 додали покращений пакет math/rand/v2 на заміну math/rand. Тепер не потрібно додавати rand.Seed(time.Now().UnixNano()) перед використанням math.IntN(10).

Дякую, гарне доповнення до статті

Браво, містер Пайк! Ще десять пакетів замість того, щоб добавити простий тернарний оператор. Або навіть елвіс-оператор.

Ну якщо є бажаня то можно зробити контібьюшен та полегчити усім життя ))

В Го це трохи не так працює. Вся ідеологія мови базується на тому, що сам Роб Пайк вважає за потрібне. От він не вважає, що тернарний оператор потрібен Го. Про це навіть є згадка в офіційному FAQ та десятки забракованих пропоузалів (наприклад, github.com/golang/go/issues/33171).

Мені дечим навіть подобається ця ідеологія, що ядро мови залишається простим, а те, що можна вирішити бібліотекою, вирішується нею.

Go, звісно, від цього краще не стає, але що вже ж поробиш.

Підписатись на коментарі