Let My Gophers Go!
229 subscribers
3 photos
22 links
Пришел в Go из Python и Data Science без особого бэкграунда в C/C++/Java.

Можно с натяжкой сказать, что пишу на Go в течение нескольких месяцев, и поэтому все написанное здесь стоит воспринимать как попытку разобраться и поделиться мыслями в процессе
Download Telegram
О канале

Кто-то однажды сказал, что лучший способ разобраться в предмете — не задать вопрос, а опубликовать абсолютно неверный ответ :)

Цели вводить в заблуждение у меня, конечно, нет, но мотивация похожая. Как понятно из описания канала, ни о какой авторитетности с моей стороны и речи быть не может. В канале я буду писать обо всем, в чем мне лично хотелось бы разобраться/не кажется очевидным. Я достаточно усерден (а иногда и чрезмерно дотошен) в плане ресерча и устранения неопределенности, поэтому таких моментов может быть достаточно много.

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

Другое дело — записывать как сам вопрос, так и свой "ответ" с результатами маленького исследования вопроса, к которым всегда можно вернуться. В общем, даже самый тупой карандаш лучше самой острой памяти :)

Прошу воспринимать все написанное здесь максимально строго и критично. Любой фидбэк крайне приветствуется, ведь именно это и является целью канала.

В свою очередь, буду периодически делиться всем полезным и интересным, что нахожу про Go (или не только). Читаю я регулярно, много, на двух языках, поэтому можно ожидать обновлений.

Let My Gophers Go! :)
Let My Gophers Go! pinned «О канале Кто-то однажды сказал, что лучший способ разобраться в предмете — не задать вопрос, а опубликовать абсолютно неверный ответ :) Цели вводить в заблуждение у меня, конечно, нет, но мотивация похожая. Как понятно из описания канала, ни о какой авторитетности…»
Pass by value

Интересно, что в некоторых туториалах и постах про Go по-прежнему пытаются объяснить поведение кода, используя фразу "passed by reference".

Go FAQ весьма недвусмысленно констатирует:

As in all languages in the C family, everything in Go is passed by value

Нет никакого pass by reference в Go, о чем кратко, но очень понятно написал Dave Cheney:
https://dave.cheney.net/2017/04/29/there-is-no-pass-by-reference-in-go

Наверное, именно эта статья навсегда закрыла этот вопрос для меня и отбила желание искать некорректные аналогии.

С другой стороны, раз уж я пришел в Go из Python, стоит ещё отметить интересный момент: в Питоне тоже все передается по значению, с одной маленькой ремаркой: каждое значение является референсом.

В английском это звучит более складно и смешнее: everything in Python is passed by value, but all values are references :)

Я бы позволил себе смелость заявить, что Питон в каком-то смысле делает выбор за программиста и лишает возможности выбрать (и тут в канал влетает Bill Kennedy) семантику. При всей любви к Питону, мне больше нравится иметь возможность самому сделать выбор, а также лучше понимать, что на самом деле будет происходить.
👍2
Zen of Go

Очень велик соблазн узнать, как писать идиоматичный, каноничный код на Go (да и вообще в целом).

Этот вопрос неоднократно появляется у меня, неоднократно всплывает на reddit.com/r/golang, и ответ обычно сводится к следующему:

— Прочитать Effective Go
— Прочитать Go Code Review Comments
— Посмотреть и прочитать Go Proverbs
— Почитать исходники
— Обратить внимание на upspin (авторитетно, потому что у истоков стояли Rob Pike и Andrew Gerrand)
— Обратить внимание на проекты в репозитории Hashicorp (не берусь судить), но их часто рекомендуют в качестве "хорошего кода на Go"

Обычно я почти ко всем источникам возвращаюсь по мере усвоения информации, и в этом случае происходит то же самое: не один раз перечитал Effective Go и Go Code Review Comments (и продолжаю, безусловно) — и каждый раз кажется, что в i+1-й раз понял написанное лучше или вообще иначе, нежели в i-й.

Но за всеми рекомендациями и "пословицами" в мире Go, которые сформулировал Роб Пайк, стоят более крупные сущности — ключевые ценности и принципы языка и экосистемы. Так называемый "Go-дзен".

Питонисты знают о "пасхалке" в виде import this, которая содержит "дзен Питона", разработанный Тимом Питерсом. Например:

— Beautiful is better than ugly
— Explicit is better than implicit
— Simple is better than complex, etc.

Dave Cheney (кажется, я, как и многие, его фанат) очень интересно проводит параллели и пытается найти соответствующие идиомы и принципы в Go. Примеров кода почти нет, но все равно познавательно.

https://dave.cheney.net/2020/02/23/the-zen-of-go

P.S. Возьму на себя смелость и скажу, что в какой-то момент кажется, что Go в большей степени соответствует "дзену" Питерса, чем сам Питон 🤷‍♂️
👍2
Go Method Sets

Уже в Tour of Go можно узнать, что Go достаточно либерален в плане вызова методов, поэтому все 4 вызова ниже возможны:

type Point struct{ X, Y float64 }

func (p Point) Distance(q Point) float64 {
...
}

func (p *Point) ScaleBy(factor float64) {
...
}

p := Point{1, 2}
q := Point{1, 1}
pptr := &Point{1, 2}

p.Distance(q)
p.ScaleBy(2)

pptr.Distance(q)
pptr.ScaleBy(2)

Причина достаточно проста и описана в спецификации:

The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T)

A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()

Если коротко, "множество методов" для типа *T включает в себя множество методов, определенных как с T, так и *T получателем. Это логично, ведь всегда можно получить значение, разыменовав указатель. Поэтому на самом деле вызов pptr.Distance(q) будет преобразовано в (*pptr).Distance(q).

Более того, для addressable типов можно не писать (&x)., компилятор сделает это за нас.
Как говорит Bill Kennedy, "Go loves you" :)

Тем не менее, поведение ниже меня в первый раз удивило:

type Vectorizer interface {
Distance(q Point) float64
ScaleBy(factor float64)
}

var _ Vectorizer = p


./prog.go:33:6: cannot use p (type Point) as type Vectorizer in assignment:
Point does not implement Vectorizer (ScaleBy method has pointer receiver)

Текст ошибки весьма однозначно описывает проблему: "... Point does not implement Vectorizer (ScaleBy method has pointer receiver)". Починить достаточно просто — var _Vectorizer = &p, но интереснее всего понять причину ошибки.

Читаем спецификацию:

A value x is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:

...

T is an interface type and x implements T

Даже это ещё не все. Даже если бы такое присвоение было корректным, не выполняется условие из параграфа Method calls: If x is addressable...

Снова читаем спецификацию:

The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal. If the evaluation of x would cause a run-time panic, then the evaluation of &x does too.

Интерфейсы не являются addressable типом.

Отдельный пункт про method sets есть и в Go FAQ:

Even in cases where the compiler could take the address of a value to pass to the method, if the method modifies the value the changes will be lost in the caller. As an example, if the Write method of bytes.Buffer used a value receiver rather than a pointer, this code:

var buf bytes.Buffer
io.Copy(buf, os.Stdin)
would copy standard input into a copy of buf, not into buf itself. This is almost never the desired behavior.

В отличие от предыдущего случая, здесь имеем переменную buf, которая (см. цитату выше) является addressable. Потенциально компилятор может взять адрес переменной и совершить свою маленькую магию, но это будет означать, что копирование произойдет в копию buf, а исходная переменная никакие изменения не увидит.

Передаем указатель сами и все снова работает: io.Copy(&buf, os.Stdin)

Effective Go этот момент тоже комментирует:

This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake.

Теперь кажется, что стоило всего лишь раз внимательно прочитать спецификацию, но разве герои идут в обход :)
🔥6👍3
Clean Architecture: The Go Way

"Clean architecture" — философский камень программирования, про который почти регулярно спрашивают на reddit.com/r/golang, а ответ обычно сводится либо к (отчасти необъяснимой) ненависти к Uncle Bob, либо к раздражению в связи с попыткой чрезмерно усложнить Go и привнести в него кусочки Java.

Саму книгу я несколько раз честно пытался читать, но все время было ощущение, что можно было вполне обойтись одной статьей.

Про чистую архитектуру в Go написано немало, но от некоторых постов и якобы "правильных" реализаций складывалось ощущение, что это действительно слишком сложно, абстракции ради абстракций. В общем, Роб Пайк бы не одобрил. Simplicity is complicated.

Какие ресурсы показались мне понятными/адекватными/занятными:

https://appliedgo.net/di/
Статья от Christoph Berger про dependency injection (когда-то я купил курс Кристофа Applied Go, который в целом очень рекомендую). Никаких революционных мыслей, немного игрушечные примеры, но все очень понятно и доступно, с мотивацией, когда в конце поста ты не просто запомнил классные английские словосочетания, но ещё и понял, зачем это применять на практике.

https://dave.cheney.net/2016/08/20/solid-go-design
Dave Cheney размышляет о принципах SOLID в контексте Go.

https://www.calhoun.io/moving-towards-domain-driven-design-in-go/
Jon Calhoun достаточно известен своими курсами, а в этой статье размышляет на тему DDD и Clean Architecture.

https://changelog.com/gotime/102
Очень крутой эпизод Go Time с Peter Bourgon (который недавно стал persona non grata из-за чрезмерной любви к троллингу), Kat Zien и Ben Johnson.

https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1#.o681bumjb
Кстати о Бене Джонсоне: этот его пост обычно первым делом рекомендуют в ответ на вопрос об организации кода.

https://www.ardanlabs.com/blog/2017/02/design-philosophy-on-packaging.html
Куда же без мнения Билла Кеннеди.

https://threedots.tech/go-with-the-domain/
Бесплатная книга Go With the Domain — объясняет DDD и Clean Architecture в процессе рефакторинга реального приложения (только начал читать, если книга — отстой, пишите в комментариях)

Интересно, что, если загуглить "Go project layout", первой же ссылкой видим Standard Go Project Layout . И тут же второй ссылкой — тикет от Russ Cox, где Расс сожалеет о том, что эту ссылку многие считают официальной рекомендацией :)

Если знаете другие классные на ваш взгляд материалы, буду благодарен, если поделитесь в комментариях 😊
👍9🔥1
Functional Options in Go

Прочитал статью про различные способы инициализации структуры и вспомнил про другую от Dave Cheney.

В этот раз написал пост в виде странички Notion, потому что там и писать удобнее, да и выглядит аккуратнее.

Поставьте 🤮 под этим сообщением, если читать так неудобно, или это привело к тому, что вы даже не стали открывать страницу :)

https://wdesert.notion.site/Functional-Options-in-Go-ebb47f62f6ae46ffa3c76ec76aba645a
🤮15👍12
Занудство о методах и попытки понять Расса

Читал вчера статью Russ Cox об интерфейсах. Использую несовершенную форму глагола, потому что, несмотря на понимание в общих чертах, не могу сказать, что усвоил написанное (когда-нибудь придется сделать перевод-пересказ, чтобы проверить своё понимание).

Споткнулся, к своему сожалению, достаточно рано (см. изображение).

Когда речь зашла о списке указателей на функции, появилось следующее method expression (согласно спецификации): (*Binary).String.

Расс называет это function pointer. В Go функции — первоклассные граждане и референс-типы, поэтому явный указатель не нужен.

To call s.String(), the Go compiler generates code that does the equivalent of the C expression s.tab->fun[0](s.data): it calls the appropriate function pointer from the itable, passing the interface value's data word as the function's first (in this example, only) argument.

Суть написанного в том, что при вызове нужного метода передается не само значение внутри интерфейса, а второе слово interface value — то есть, указатель на него.

И указатели на функции внутри itable (первое слово interface value) на самом деле имеют вид (*Binary).String.

Раздел спецификации про method expressions разъясняет:

type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // value receiver
func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver

var t T

Вызов t.Mv(7) эквивалентен T.Mv(t, 7) и аналогично (&t).Mp(10) эквивалентен (*T).Mp(t, 10).

Для метода с value-получателем существует также сигнатура с указателем:

func(tv *T, a int) int, в которой подразумевается indirection/dereference (то есть, разыменование) указателя и вызов: func(tv T, a int) int.

Это как раз и гарантирует, что мы можем вызвать метод, определенный с value receiver даже для указателя — компилятор возьмет значение и вызовет нужную функцию.

Четвертый сценарий невозможен, так как множество методов для типа T не включает в себя методы, определеленные для *T. Исключение: для обычных вызовов (неинтерфейсных) компилятор в состоянии добавить (&t). за нас. Это возможно, правда, только для типов, у которых можно взять адрес (addressable), например, у переменной.

Если подытожить: вся магия в том, что из 4 возможных комбинаций value/value, value/pointer, pointer/value, pointer/pointer существует 3 сигнатуры функции, которые позволяют подстроиться. Исключением является только случай, когда мы пытаемся вызвать метод с pointer-получателем, передавая value type.

Это, конечно, все занудство и, очень вероятно, где-то выше я исказил то, что реально написано в спецификации и у Расса (прошу поправлять в комментариях, если это так).

В книге Go101 в разделе про методы про это тоже написано, но используется терминология, которой нет в спецификации и официальных блог-постах (method normalization, boxing/unboxing), а это немного смущает, хотя очень впечатляет подробность.

Заранее спасибо, если укажете на заблуждения, ошибки, неточности и другие интересные ресурсы :)
👍4🤔1
Hypothes.is

Небольшой оффтопик про чтение статей. Нашел прикольный инструмент, который позволяет делать аннотации и хайлайты в статьях и, что ещё более удобно, в PDF-файлах.

Очень актуально, когда нужно в чем-то разобраться, источников несколько (например, в случае выше — блог-пост, спецификация, ещё несколько статей) и понимание приходится складывать по кусочкам :)
👍2
Go Time!

Go Time — классный подкаст, посвященный Go и всему, что с ним связано. Иногда приходят даже члены Go Team и участвуют в AMA (ask me anything) сессиях.

Один из самых интересных эпизодов — Creating the Go Programming Language с участием Роба Пайка и Роберта Гризмера.

Мне не так легко в плане концентрации даются длинные подкасты, но, к счастью, у Go Time для каждого эпизода есть транскрипт.

Например, эпизод про создание Go можно не слушать, а прочитать здесь.

Помимо прочего, это ещё и отличная возможность начать активно слушать об интересной для себя тематике на английском. Все непонятное и "нерасслышанное" можно подсмотреть в скрипте и разобрать, если неизвестен перевод или правильное произношение.

Уже за несколько эпизодов можно сильно поднять как свой "технический" английский, так и восприятие на слух — а это очень пригодится на собеседовании, например. В общем, win-win.

Приятного прослушивания и чтения :)
Что ты за тип?

"Если не учил физику в школе читал спецификацию Go, вся жизнь будет наполнена чудесами и волшебством"

var _ Fooer = (*Foo)(nil)

Строчка кода выше, если особо не задумываться, не кажется очень осмысленной. Blank identifier, nil, конвертирование типа — сразу несколько финтов, но ради чего?

Как это обычно бывает, ответ можно найти в Go Spec/Effective Go/Go FAQ. На этот раз — в последнем:

You can ask the compiler to check that the type T implements the interface I by attempting an assignment using the zero value for T or pointer to T, as appropriate:

type T struct{}
var _ I = T{} // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.

Несмотря на то, что в compile-time и так происходит проверка на реализацию нужного интерфейса, можно сделать это и самостоятельно при желании.
В примере выше происходит явная проверка на то, что типы T и *T реализуют интерфейс I.

При этом помним, что вторая проверка с (*T)(nil) сильнее: она точно окажется успешной, если T реализует интерфейс I. Обратное — неверно (см. пост выше про method sets).

Таким образом: var _ Fooer = (*Foo)(nil) конвертирует untyped nil в nil типа *Foo и пытается присвоить переменной типа I. Это, согласно спецификации, возможно, только если method set для конкретного типа в правой части assignment statement шире, чем method set интерфейса.

Можно было бы ещё реализовать проверку так:

var _ Fooer = &Foo{}
var _ Fooer = new(Foo)

Это, однако, приводит к ненужному в данном случае выделению памяти, поэтому Go FAQ даже их не упоминает.

Eli Bendersky пишет, если кратко, что смысла в этом не так много, но подобный трюк может пригодиться, если нужно в конкретном участке кода получить ошибку компиляции, если каким-то образом изменится сам интерфейс.

Также Eli напоминает про возможность runtime-проверки:

var f Foo
_, ok := interface{}(f).(Fooer)

Конвертирование к типу interface{} (его реализуют все, потому что interface{} says nothing) нужно, так как type assertions работают только для интерфейсных типов.
7
Internals of Go

Меня очень впечатлила статья Расса про интерфейсы: с одной стороны, она достаточно подробная, но при этом легко читается и по стилю похожа на аннотированную спецификацию, где техлид проекта пытается объяснить, что и как работает.

Пытался найти что-нибудь подобное для просветления. Вот, что пока удалось найти:

Golang Internals Resources
Репозиторий с подборкой статей для тех, кто хочет знать, что происходит под капотом

Go Internals
Незаконченная книга по подкапотным деталям Go. Пока что показалась слишком хардкорной, судя по тому, что первая глава называется "assembly_primer". Надо набраться смелости и когда-нибудь ее все-таки прочитать :)

Вопрос на SO про изучение внутренностей Go
Отвечает господин Друзь один из самых активных участников SO в Go Language Collective — icza и приводит список must-read статей. Список частично перекликается с репозиторием в первом пункте.

Если есть что-то ещё на примете, кроме статей и видео по ссылкам выше — кидайте, будет круто :)
👍8
Go Gotchas

В своём первом посте делился ссылкой на Notion-страничку, где собираю классные ресурсы по Go. Один из блогов в списке — divan.dev, в котором есть неплохие статьи, например, с визуализацией concurrency.

В статье How to Avoid Go Gotchas автор формулирует следующую мысль: большая часть так называемых "gotchas" (грубо говоря, "ловушек") связана с непониманием чего-то фундаментального.

И дальше автор объясняет то, что, на его взгляд, обязательно нужно понимать, чтобы реже удивляться :)

В целом, ничего нового и сложного: указатели, "все передается по значению", слайсы — структура из 3 слов, среди которых указатель на массив, интерфейсы. В общем-то, в постах выше я кратко разбирал соответствующие части спецификации.

Но один момент из статьи, несмотря на простоту, потребовал у меня времени на осознание: то, как работает append и что происходит в результате с копиями слайса.

Кажется, мой канал можно было смело назвать "Just read the specs" :)

If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.

Все просто: если len(slice) + len(data) > cap(slice), выделяется новый underlying-массив подходящего размера, готовый вместить в себя старые + новые элементы.

Что такое подходящий размер? Пока размер массива под капотом < 1024, будет выделяться в 2 раза больший массив, но после отметки в 1024 элемента — будет происходить увеличение размера в 1.25 раз.

Почему именно так? Отвечает Роб Пайк:

You need to pick something.

It was just arbitrary, I'm sure. 1024 is a nice number, and it's larger than the length of many slices.

Sometimes a number is just a number.

Иногда банан число всего лишь число. Справедливо :)

Append (немного) хитёр:

func main() {
s := make([]int, 0, 5)
s = append(s, []int{1, 2, 3, 4}...)

a := append(s, 5)
fmt.Println(a)

b := append(s, 6)
fmt.Println(b)
fmt.Println(a)
}

После таких нехитрых манипуляций получим:

[1 2 3 4 5]
[1 2 3 4 6]
[1 2 3 4 6]

С другой стороны, если поменять s := make([]int, 0, 5) на s := make([]int, 0, 4), получим:

[1 2 3 4 5]
[1 2 3 4 6]
[1 2 3 4 5]

Фрагмент из спецификации все объясняет: если capacity слайса хватает, исходный underlying-массив будет переиспользован, что и происходит в первом случае, где capacity = 5.

Во втором случае capacity = 4, что меньше len(slice) + len(data) = 4 + 1 = 5. В результате выделяется новый кусок памяти под массив для 8 элементов, а len и cap нового слайса принимают значения 5 и 8 (то самое удвоение), соответственно.

Поведение абсолютно понятное, но слегка коварное.

Интересно, что сам автор статьи в одном из примеров то ли опечатался, то ли тоже запутался:

a := make([]int, 32)
b := a[1:16]
a = append(a, 1)
a[2] = 42

Иллюстрация под кодом говорит о том, что len(b) = cap(b) = 15, но на самом деле: len(b) = 15, cap(b) = 31.

P.S. Кстати, солидно обновил список ресурсов по Go в Notion, если вдруг кто пропустил :)
🔥6👍4
Alias declaration vs type definition

type Test = string
type Test string

Если присмотреться, можно заметить, что это все-таки 2 разные строки :) При этом первая выглядит достаточно непривычно, в то время как вторая — конструкция, знакомая и понятная.

Идем за разъяснениями в спецификацию:

Alias declarations
An alias declaration binds an identifier to the given type.

Within the scope of the identifier, it serves as an alias for the type.

type (
nodeList = []*Node // nodeList and []*Node are identical types
Polar = polar // Polar and polar denote identical types
)

Согласно спецификации, Polar и polar — один и тот же тип, мы всего лишь создали "алиас".

С другой стороны:

A type definition creates a new, distinct type with the same underlying type and operations as the given type, and binds an identifier to it.

The new type is called a defined type. It is different from any other type, including the type it is created from.

type (
Point struct{ x, y float64 } // Point and struct{ x, y float64 } are different types
polar Point // polar and Point denote different types
)

В случае с type definition мы объявляем новый тип (defined type), на основе выбранного underlying type.

В примере выше polar — defined type, Point — underlying type.

Таким образом, есть две формы декларации типа (type declaration):

1) Декларация алиаса (alias declaration)
2) Определение типа (type definition)

Синтаксически они отличаются лишь знаком равенства, но никакого смыслового равенства нет.

И остается один вопрос: зачем вообще использовать type aliases?

Обращаемся к выступлению Расса под названием "Codebase Refactoring (with help from Go)", где написано:

5.3. Type aliases
To enable gradual code repair during codebase refactorings, it must be possible to create alternate names for a constant, function, variable, or type. Go already allows introducing alternate names for all constants, all functions, and nearly all variables, but no types. Put another way, the general alias form is never necessary for constants, never necessary for functions, only rarely necessary for variables, but always necessary for types.

type OldAPI = NewPackage.API

Если подытожить: это явно не то, чем придется регулярно пользоваться, но такой синтаксический финт может пригодиться во время масштабного рефакторинга кодовой базы.
👍3
Maps in Go

Map — структура данных, которая важна сразу по нескольким причинам: с одной стороны, — это классическая структура данных, без которой сложно представить программирование, и, с другой, само ее устройство может стать предметом разговора на собеседовании (вопрос популярный, проверено).

Устроена она несколько сложнее, чем массивы (константную сложность надо заслужить), но это все ещё не rocket science, если не вдаваться в криптографию.

Вероятно, это не самая точная аналогия, но будем для простоты терминологии называть map —своего рода "интерфейсом", который определяет нужное для нас поведение и свойство: O(1) lookups (то есть, доступ по ключу) в среднем (это значит, что может быть и хуже, если выбранная хеш-функция звезд с неба не хватает).

В Go этот "интерфейс" реализован как hashmap. Эта структура данных используется в Go рантайме.

Согласно Go FAQ, реализация настолько хороша, что в большинстве случаев о ней не придется задумываться:

The same reason strings are: they are such a powerful and important data structure that providing one excellent implementation with syntactic support makes programming more pleasant. We believe that Go's implementation of maps is strong enough that it will serve for the vast majority of uses.

Но это нас не остановит, конечно же.

Сначала заметим, что map в Go — это указатель на runtime.hmap. Иначе говоря, это так называемый reference/reference type, но никакого pass by reference по-прежнему нет: копируется указатель, а не сами данные.

Это стоит иметь в виду, и об этом предупреждает Effective Go:

Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.

Кстати, когда-то вместо map[K]V предполагалось использовать *map[K]V, но потом, как пишет Ian Lance Taylor, стало ясно, что приходится всегда писать *map, это нудно, и поэтому синтаксис упростили.

Очень рекомендую статью Dave Cheney про устройство `map`. В ней рассказано:

— Про саму структуру данных без привязки к какому-либо языку

— В каком виде map присутствует в ряде популярных языков

— Какие есть нюансы реализации в самом Go: например, при их реализации обошлись без дженериков, interface{} и кодогенерации

Если нужно для собеседований или по какой-то другой причине глубже разобраться в структурах данных и основах теории сложности алгоритмов (все эти о-малые, О-большие, теты и омеги), могу порекомендовать книгу Скиены — Algorithm Design Manual. Как по мне, идеальный баланс между теорией и практикой.
👍7
Maps in Go (addendum)

Кстати, в статье Dave Cheney есть один момент, который меня немного смущает.

В примерах применения хеш-функции говорится о "masking off the lower 3 bits", что по сути означает (должно означать в моем понимании) операцию AND с 111, но полученные номера бакетов почему-то не соответствуют такой логике (см. примеры из статьи).

Возможно, речь о чем-то другом и тогда буду благодарен, если кто-то в комментариях разъяснит :)
Ресурсы по БД

Небольшой оффтоп, посвященный БД с некоторым (думаю, понятным) перекосом в пользу PostgreSQL.

Скажу банальность: SQL — lingua franca для всех, кто работает с данными, поэтому очень полезно понимать не только синтаксис и базовые селекты, которым можно научиться за пару часов, но и более тонкие нюансы.

Ресурсы, которые я когда-либо читал/смотрел (и до сих пор периодически возвращаюсь):

Фантастический курс по устройству баз данных от CMU
Это не самая новая версия курса, но очень советую слушать именно версию Andy Pavlo. Он потрясающий преподаватель, который обожает то, чем занимается, и это чувствуется. Содержание курса тоже 🔥
На сайте доступны слайды и конспекты по курсу.

— По самому PostgreSQL, кажется, нет ничего лучше, чем официальная документация. Можно начать с туториала, и дальше использовать уже в качестве справочника. Написано очень подробно и доступно.

— В документации очень подробная глава про индексы, их разновидности и применение, но самый классный ресурс на тему производительности и использования индексов в частности — Use the Index, Luke!. Незаменимый ресурс, если нужно разобраться в использовании индексов на практике.

Мини-книжка по SQL optimization от Dataschool. Можно прочитать ее до Use the Index, Luke, чтобы сразу быть в контексте. Но тут без особой глубины.

— Попрактиковаться очень удобно на PostgreSQL Exercises. Каждая задача сопровождается чуть ли не построчным объяснением. Классный формат, совмещающий теорию и практику.

— Оконные функции считаются (не уверен, что по праву) своего рода инициацией в advanced-зону, поэтому стоит один раз в них разобраться и набить руку. Это можно сделать на специальном сайте, который и теорию объяснит, и задачки подкинет.

— И наконец хардкор. Зубодробительные внутренности PostgreSQL, где автор предполагает, что его целевая аудитория — администраторы БД. Начал читать, но, по правде говоря, это та книга, которую точно лучше использовать как справочник. Захотелось узнать, как обрабатываются запросы в любимом PG — открыли третью главу, разобрались, на следующий день все забыли.
🔥11👍1
Distributed Systems

Продолжаем рубрику "я и моя нездоровая любовь к спискам".

На этот раз — список для изучения system design/scalability/distributed systems (в общем, путь в Google). Как мог сказать бы в наше время Ломоносов: "System design уже затем учить надо, что он ум в порядок приводит".

Область огромная, непростая, но очень интересная. К тому же, если вдруг придется проходить собеседования в FAANG (или похожие компании с многоэтапными собеседованиями), эти знания очень пригодятся.

— Нельзя просто так взять и не начать с Designing Data-Intensive Applications aka "кабанчика". Эта книга Мартина Клеппемана — одна из самых часто рекомендуемых книг в области IT в целом, но дело не в популярности, а в том какая она крутая. Постоянно к ней возвращаюсь и понимаю по-новому по мере обретения опыта :)

— Мартин Клеппеман не так давно выложил ещё и свой курс по распределенным системам, который читает в Кембридже. Во многом лекции перекликаются с книгой, но есть также и видеозаписи, которые могут понравиться тем, кто лучше усваивает информацию из видео :)

System Design Primer
Невероятно крутой репозиторий с джентльменским набором знаний в области system design. Уступает, конечно, в глубине книгам, но для собеседования или понимания в общих чертах просто идеально.

Web-Scalability for Startup Engineers
Менее известная, но тоже замечательная книга, которая объясняет system design от лица стартапа, который по мере роста сталкивается с необходимостью масштабироваться.

The Architecture of Open Source Applications
Основы распределенных систем с реальными примерами в open source мире.

Distributed Systems for Fun and Profit
Небольшая, но классная книга, с которой можно начать.

MIT: 6.824 Distributed Systems
Крутой курс от MIT, который, кстати, использует Go. Всегда хотел его посмотреть до конца, но видео мне даются тяжело, поэтому в основном читаю, а видео смотрю лишь эпизодически.

Пост Robert Heaton с практичным введением в system design. Захватывающая проза с шутками и историями из жизни.

Похожий пост от Gergely Orocz про основные понятия из system design, которые автору пришлось усвоить во время работы над платежной системой: SLA, идемпотентность — все на месте :)

Highscalability
Блог с разбором архитектур реальных высоконагруженных сервисов.
🔥12
Rethinking Classical Concurrency Patterns

Иду по официальному списку для изучения concurrency in Go. Цель — добиться того, чтобы не только понимать уже написанный код, но и понимать, как писать конкурентный код на Go грамотно и эффективно и, что не менее важно, понимать, когда его писать не надо (какой-то Воннегут получился).

Один из пунктов списка — очень крутое выступление Bryan Mills (работает над Go) про concurrency patterns.

Слушать Брайана — одно удовольствие, но можно и почитать слайды с подробной расшифровкой речи.

Хорошего воскресенья :)
🔥7