Это перевод статьи юзернейма tucnak с Medium, которая получила обширное обсуждение на reddit.com/r/programming.
Окей, заголовок действительно несколько громкий, признаю. Cкажу больше: я прусь от громких заголовков, все из-за внимания. В этой блогозаписи я постараюсь доказать тот факт, что Go это ужасно продуманный язык (спойлер: это так). Я уже играюсь с Go уже на протяжении нескольких месяцев, первый helloworld собрал, кажется, в июне. Математик из меня никакой, но с тех пор прошло уже что-то около 4 месяцев и я даже успел залить на Github несколько репозиториев и собрать немного звезд! Стоит также упомянуть, что у меня совершенно нет опыта применения Go в продакшне, так что любые мои слова о «поддержке кода» или «деплое» не стоит принимать за единственноверную истину.
Я люблю Go, я полюбил его как только впервые попробовал его. Я потратил несколько дней на то, чтобы принять идиоматику, смириться с отсутствием дженериков, разобраться с откровенно странным способом обработки ошибок и вы знаете, всеми этими классическими проблемами, так или иначе связанными с Go. Я прочел Effective Go, много статеек из блога Dave Cheney, следил за всеми новостями из мира Go. Я даже могу сказать, что я достаточно активный участник сообщетсва! Я люблю Go и ничего не могу с этим поделать — Go просто замечательный. Тем не менее, я считаю, что Go это ужасный плохо продуманный язык, который делает совершенно не то, что «продает».
Go считается простым языком программирования. Как сказал Rob Pike, они «убрали из языка все, что только можно было убрать», что сделало его спецификацию просто тривиальной. Эта сторона языка просто удивительна: ты можешь выучить основы в течении минут, сразу начать писать реальный код и и в большинстве случаев Go будет себя вести ровно так, как ты ожидаешь. Ты будешь много беситься, но к счастью, все будет работать круто. В реальности все немного иначе, Go это далеко не простой язык, скорее просто плохой. А теперь взгляните на несколько аргументов, которые подтверждают мои слова.
Cлайсы (это такие навороченные массивы) очень классные, мне безумно нравится концепт и непосредственно реализация. Но давайте на одну секундочку представим, что нам какой-то момент захочет написать с ними немного исходного кода, может быть совсем чуть-чуть. Слайсы это сердце языка, это один из тех концептов, который делает Go крутым. Но все же, давайте все-таки представим, что как-то вовсе вдруг, в перерывах между разговорами о «концептах», мы захотим написать чуток реального кода. Вот то, что нам предлагает в данном случае язык Go:
Хотите верьте, хотите — нет, но именно так гоферы каждый день трансформируют слайсы. А так как у нас нет никаких этих ваших дженериков, то написать красивую функцию insert(), которая будет прятать весь этот ужас просто не получится. Я залил этот рабочий пример нa playground, так что не стоит мне верить: можешь проверить своими руками.
Они говорят нам, что «ошибки в Go это больше, чем просто строки» и что мы не должны обращаться с ними, как со строками. Например, spf13 из Docker сказал об этом во время превосходного выступления на тему «7 common mistakes in Go and when to avoid them».
Они также говорят, что нам стоит всегда возвращать ошибки через интерфейс error (однородность, читабельность, и так далее). Это в точности то, что я делаю в сниппите кода ниже. Ты наверняка будешь удивлен, но эта программа действительно поздоровается с Мистером Пайком, но все ли так, как должно быть?
Да, я в курсе, почему это происходит, так как я прочел достаточное количество профильной литературы про устройство интерфейсов в Go. Но согласитесь, для начинающего гофера подобное шапито будет как обухом по голове. В действительности это известная западня. Как вы видите, Go это настолько прямолинейный и простой для изучения язык без плохих отвлекающих фич, что иногда он считает, что нулевые интерфейсы не очень-то и нулевые ;)
В случае если вы не в курсе, что это такое, то давайте я все-таки процитирую Википедию: “сокрытие переменных происходит тогда, когда переменная определенная в определенном контексте (условный блок, метод, внутренний класс) имеет такое же имя, как и переменная во внешнем контексте”. Звучит толково, вроде как довольно известная практика, большинство языков программирования поддерживают сокрытие и вс окей. Интересно, но Go не исключение, но тут дела состоят несколько иначе: у нас есть оператор :=, который добавляет веселья. Вот как работает сокрытие переменных в Go:
Да, я в курсе, что оператор := создает новую переменную и задает ей значение по типу справа и то, что в соотв. со спецификацией языка это абсолютно корректное поведение. Но понимаете ли, стоит убрать внутренний контекст (фигурные скобки) и код заработает ровно так, как мы ожидаем («after 42»). Интересно девки пляшут, не так ли? В другом случае вам придется играться с замечательными сокрытием переменных.
Следует отметить, что это все не просто забавный пример, который я придумал за обедом, это реальная ловушка, в которую люди время от времени попадают далеко не по своей вине. На этой неделе я рефакторил немного кода на Go и встретил эту проблему дважды. Компиляторам все равно, линтерам все равно, всем все равно, но код работает неправильно.
Интерфейсы классные, Pike&Co. продолжают говорить, что они это то, чем является Go: интерфейсы решают проблему дженериков, мы используем интерфейсы для тестов, через них в Go построен полиморфизм. Говорю вам, я полюбил интерфейсы прямо во время прочтения «Effective Go» и продолжаю их любить. Тем не менее, помимо проблемы «этот нулевой интерфейс не совсем нулевой», о которой я говорил чуть ранее, существует другая неприятная проблема, которая сконяет меня к мысли, что в Go нет полноценной поддержки интерфейсов. Простыми словами, ты просто не можешь передать слайс структур (которые удовлетворяют определенный интерфейс) в функцию, которая принимает слайс этого интерфейса:
Совершенно неудивительно, что это известная проблема, которая даже проблемой-то не считается. Это просто очередная забавная штука в Go, окей? Я очень рекоммендую прочесть соотв статью в Wiki по теме, станет ясно, почему трюк с передаванием []struct как []interface не прокатит. С другой стороны, подумайте об этом! Мы можем это делать, тут нет никакой магии, это просто проблема компилятора. Посмотрите, прямо здесь, чуть выше в коде я это сделал вручную. Почему компилятор Go не может заниматься этим шапито вместо меня? Да-да, явное лучше чем неявное, но все же?
Я просто не могу терпеть то, как люди смотрят на дерьмо вроде этого, которого в языке просто полно и продолжают говорить «ну да, это норм». Это не норм. Это то, что делает Go ужасным языком программирования.
UPD: Это несколько надуманная проблема, если смотреть со стороны языка. А если не смотреть, то все равно, т.к. мы же говорим о косяках непосредственно языка, а не документации и экосистемы.
Это вообще первая проблема, с которой я когда-либо столкнулся в Go. Окей, у нас тут есть «for-range» цикл, который позволяет ходить по слайсам и слушать каналы. Его используют повсюду и это окей. Вот вам еще одна незначительная, но не очень приятная проблема, с которой сталкивается большинство неопытных неофитов: range-цикл поддерживает итерирование только «по значению», он просто копирует значения и ты ничего не можешь с этим поделать — это тебе не foreach из С++.
Прошу отметить, я не ругаюсь на то, что в Go нет range-циклов «по ссылке», я ругаюсь на то, что эти range-циклы довольно неочевидны. Глагол “range” будто говорит “пройтись по элементам”, но не очень говорит “пройтись по копиям элементов”. Давайте посмотрим на секцию For из “Effective Go”, в ней ни слова про то, что range-циклы копируют значения слайса, там просто об этом не написано. Я согласен, что это очень незначительная проблема, к тому же я в свое время с ней очень быстро совладал (в течении минут), но неопытный гофер может потратить некоторое время на отладку кучка кода, не понимая, почему значения в массиве не меняются. Нужно было бы хоть немного, но обрисовать это дело в “Effective Go”.
Как я уже мог говорить ранее, Go считается чистым, простым и читабельным языком программирования со строгим компилятором. Например, у тебя не выйдет скомпилировать программу с unused import. Почему? Просто потому что Мистер Пайк считает, что так должно быть. Можешь верить, а можешь и нет, но unused import это далеко не конец Света и люди могут его пережить, особенно во время активной отладки. Я полностью согласен, что это не очень правильно и компилятор просто должно выводить какое-то предупреждение, но ради всего святого, какой смысл прекращать компиляцию из-за такой мелочи? Неиспользованный импорт, серьезно?
К Go1.5 нам подогнали классное изменение языка: наконец-то можно указывать элементы словаря без явного указания типа хранимого значения. Мальчикам из Go team понадобилось чуть более пяти лет, чтобы сообразить, что явное указание типа может быть чуток избыточным.
Другая замечательная штука, от которой я прусь в Go — это коммы. Видете ли, в Go у нас есть так называемые многострочные блоки объявления (import / var / const):
Окей, но как только дело доходит до «читабельности», Роб Пайк решает, что надо ВНЕЗАПНО добавить запятые. В какой-то момент после добавления запятых, он решает, что стоит оставлять запятую на последней строчке списка. Таким образом, вместо того, чтобы писать так:
Мы вынуждены писать так:
Я до сих пор задаюсь вопросом, почему мы не можем просто обойтись без запятых на блоках import / var / const и уж никак не можем на списках или словарях. В любом случае, Роб Пайк точно шарит лучше меня, так что все окей. Да здравствует Читабельность!
Во-первых, я не имею ничего против кодогенерацию. Для бедного языка, вроде Go, это, возможно, единственный способ избежать копипасты для обобщенных штук. В любом случае, go:generate — тулза для кодогенерации в Go, которая используется гоферами по всему миру, просто отстой. Ну, чтобы уж быть до конца честным, тулза сама по себе ок, мне нравится. Проблема не в тулзе, а в самом подходе. Смотри, для того, чтобы сгенерить какой-то код, тебе нужно воспользоваться специальным магическим комментарием. Да, магическая последовательность байтов где-то в комментариях твоего кода может генерить код! Поглядите только, какое зрелищное шапито мы наблюдаем ниже:
Комментарии должны объяснять код, не генерировать его. В любом случае, магические комментарии это обыкновенная рабочая практика в современном Go. Интересно, но всем все равно, всех все устраивает, это окей. Мне лично кажется, что это намного большее зло, чем чертовы unused imports, которые валят компиляцию.
Как вы видите, я не ругался по поводу обобщений / обработки ошибок / сахара и других классических проблемах, о которых говорят в контексте Go. Я согласен, что дженерики это вовсе не мастхев, но тогда дайте нам нормальную генерацию, а не волшебные циркопляски в комментариях, которые типа как генерируют код. Если в забираете у нас исключения, то пожалуйста, дайте нам возможность нормально сверять нулевые интерфейсы с нулем! А если вы у нас «неоднозначный в плане читабельности» сахар забираете, то дайте возможность писать код, который работает ожидаемо, без «гоп ца ца» сокрытия переменных.
Так или иначе, я продолжу использовать Go. На это есть веская причина: я люблю его. Я ненавижу язык: он просто отвратителен, но я обожаю сообщество, инструментарий и львиную долю концепций, которые предлагает язык, вообще всю экосистему.
Пссс, парень, не хочешь форкнуть Go?
Окей, заголовок действительно несколько громкий, признаю. Cкажу больше: я прусь от громких заголовков, все из-за внимания. В этой блогозаписи я постараюсь доказать тот факт, что Go это ужасно продуманный язык (спойлер: это так). Я уже играюсь с Go уже на протяжении нескольких месяцев, первый helloworld собрал, кажется, в июне. Математик из меня никакой, но с тех пор прошло уже что-то около 4 месяцев и я даже успел залить на Github несколько репозиториев и собрать немного звезд! Стоит также упомянуть, что у меня совершенно нет опыта применения Go в продакшне, так что любые мои слова о «поддержке кода» или «деплое» не стоит принимать за единственноверную истину.
Я люблю Go, я полюбил его как только впервые попробовал его. Я потратил несколько дней на то, чтобы принять идиоматику, смириться с отсутствием дженериков, разобраться с откровенно странным способом обработки ошибок и вы знаете, всеми этими классическими проблемами, так или иначе связанными с Go. Я прочел Effective Go, много статеек из блога Dave Cheney, следил за всеми новостями из мира Go. Я даже могу сказать, что я достаточно активный участник сообщетсва! Я люблю Go и ничего не могу с этим поделать — Go просто замечательный. Тем не менее, я считаю, что Go это ужасный плохо продуманный язык, который делает совершенно не то, что «продает».
Go считается простым языком программирования. Как сказал Rob Pike, они «убрали из языка все, что только можно было убрать», что сделало его спецификацию просто тривиальной. Эта сторона языка просто удивительна: ты можешь выучить основы в течении минут, сразу начать писать реальный код и и в большинстве случаев Go будет себя вести ровно так, как ты ожидаешь. Ты будешь много беситься, но к счастью, все будет работать круто. В реальности все немного иначе, Go это далеко не простой язык, скорее просто плохой. А теперь взгляните на несколько аргументов, которые подтверждают мои слова.
Причина №1. Манипуляции со слайсами просто отвратительны
Cлайсы (это такие навороченные массивы) очень классные, мне безумно нравится концепт и непосредственно реализация. Но давайте на одну секундочку представим, что нам какой-то момент захочет написать с ними немного исходного кода, может быть совсем чуть-чуть. Слайсы это сердце языка, это один из тех концептов, который делает Go крутым. Но все же, давайте все-таки представим, что как-то вовсе вдруг, в перерывах между разговорами о «концептах», мы захотим написать чуток реального кода. Вот то, что нам предлагает в данном случае язык Go:
// Давайте сделаем немного чисел...
numbers := []int{1, 2, 3, 4, 5}
log(numbers) // 1. [1 2 3 4 5]
log(numbers[2:]) // 2. [3 4 5]
log(numbers[1:3]) // 3. [2 3]
// Интересный факт: отрицательные индексы не работают!
//
// numbers[:-1] из Python не прокатит. Взамен нам предлагают
// делать что-то вроде этой циркопляски с длиной контейнера:
//
log(numbers[:len(numbers)-1]) // 4. [1 2 3 4]
// “Потрясная” читабельность, Мистер Пайк! Хорош!
//
// А теперь давайте допишем шестерку:
//
numbers = append(numbers, 6)
log(numbers) // 5. [1 2 3 4 5 6]
// Самое время удалить из слайса тройку:
//
numbers = append(numbers[:2], numbers[3:]...)
log(numbers) // 6. [1 2 4 5 6]
// Хочется вставить какое-то число? Ничего страшного,
// в Go есть общепринятая best practice!
//
// Мне особенно нравится это шапито с троеточиями ...
//
numbers = append(numbers[:2], append([]int{3}, numbers[2:]...)...)
log(numbers) // 7. [1 2 3 4 5 6]
// Чтобы скопировать слайс, ты должен будешь написать это:
//
copiedNumbers := make([]int, len(numbers))
copy(copiedNumbers, numbers)
log(copiedNumbers) // 8. [1 2 3 4 5 6]
// И это еще не все.
Хотите верьте, хотите — нет, но именно так гоферы каждый день трансформируют слайсы. А так как у нас нет никаких этих ваших дженериков, то написать красивую функцию insert(), которая будет прятать весь этот ужас просто не получится. Я залил этот рабочий пример нa playground, так что не стоит мне верить: можешь проверить своими руками.
Причина №2. Нулевые интерфейсы не всегда нулевые :)
Они говорят нам, что «ошибки в Go это больше, чем просто строки» и что мы не должны обращаться с ними, как со строками. Например, spf13 из Docker сказал об этом во время превосходного выступления на тему «7 common mistakes in Go and when to avoid them».
Они также говорят, что нам стоит всегда возвращать ошибки через интерфейс error (однородность, читабельность, и так далее). Это в точности то, что я делаю в сниппите кода ниже. Ты наверняка будешь удивлен, но эта программа действительно поздоровается с Мистером Пайком, но все ли так, как должно быть?
package main
import "fmt"
type MagicError struct{}
func (MagicError) Error() string {
return "[Magic]"
}
func Generate() *MagicError {
return nil
}
func Test() error {
return Generate()
}
func main() {
if Test() != nil {
fmt.Println("Hello, Mr. Pike!")
}
}
Да, я в курсе, почему это происходит, так как я прочел достаточное количество профильной литературы про устройство интерфейсов в Go. Но согласитесь, для начинающего гофера подобное шапито будет как обухом по голове. В действительности это известная западня. Как вы видите, Go это настолько прямолинейный и простой для изучения язык без плохих отвлекающих фич, что иногда он считает, что нулевые интерфейсы не очень-то и нулевые ;)
Причина №3. Забавное сокрытие переменных
В случае если вы не в курсе, что это такое, то давайте я все-таки процитирую Википедию: “сокрытие переменных происходит тогда, когда переменная определенная в определенном контексте (условный блок, метод, внутренний класс) имеет такое же имя, как и переменная во внешнем контексте”. Звучит толково, вроде как довольно известная практика, большинство языков программирования поддерживают сокрытие и вс окей. Интересно, но Go не исключение, но тут дела состоят несколько иначе: у нас есть оператор :=, который добавляет веселья. Вот как работает сокрытие переменных в Go:
package main
import "fmt"
func Secret() (int, error) {
return 42, nil
}
func main() {
number := 0
fmt.Println("before", number) // 0
{
// meet the shadowing
number, err := Secret()
if err != nil {
panic(err)
}
fmt.Println("inside", number) // 42
}
fmt.Println("after", number) // 0
}
Да, я в курсе, что оператор := создает новую переменную и задает ей значение по типу справа и то, что в соотв. со спецификацией языка это абсолютно корректное поведение. Но понимаете ли, стоит убрать внутренний контекст (фигурные скобки) и код заработает ровно так, как мы ожидаем («after 42»). Интересно девки пляшут, не так ли? В другом случае вам придется играться с замечательными сокрытием переменных.
Следует отметить, что это все не просто забавный пример, который я придумал за обедом, это реальная ловушка, в которую люди время от времени попадают далеко не по своей вине. На этой неделе я рефакторил немного кода на Go и встретил эту проблему дважды. Компиляторам все равно, линтерам все равно, всем все равно, но код работает неправильно.
Причина №4. Ты не можешь передать []struct как []interface
Интерфейсы классные, Pike&Co. продолжают говорить, что они это то, чем является Go: интерфейсы решают проблему дженериков, мы используем интерфейсы для тестов, через них в Go построен полиморфизм. Говорю вам, я полюбил интерфейсы прямо во время прочтения «Effective Go» и продолжаю их любить. Тем не менее, помимо проблемы «этот нулевой интерфейс не совсем нулевой», о которой я говорил чуть ранее, существует другая неприятная проблема, которая сконяет меня к мысли, что в Go нет полноценной поддержки интерфейсов. Простыми словами, ты просто не можешь передать слайс структур (которые удовлетворяют определенный интерфейс) в функцию, которая принимает слайс этого интерфейса:
package main
import (
"fmt"
"strconv"
)
type FancyInt int
func (x FancyInt) String() string {
return strconv.Itoa(int(x))
}
type FancyRune rune
func (x FancyRune) String() string {
return string(x)
}
// Удовлетворяет любой структуре с методом String().
type Stringy interface {
String() string
}
// Строка, состоящая из строковых представлений всех элементов.
func Join(items []Stringy) (joined string) {
for _, item := range items {
joined += item.String()
}
return
}
func main() {
numbers := []FancyInt{1, 2, 3, 4, 5}
runes := []FancyRune{'a', 'b', 'c'}
// Такое не прокатит!
//
// fmt.Println(Join(numbers))
// fmt.Println(Join(runes))
//
// prog.go:40: cannot use numbers (type []FancyInt) as type []Stringy in argument to Join
// prog.go:41: cannot use runes (type []FancyRune) as type []Stringy in argument to Join
//
// Взамен они предлагают нам заниматься вот такой циркопляской:
//
properNumbers := make([]Stringy, len(numbers))
for i, number := range numbers {
properNumbers[i] = number
}
properRunes := make([]Stringy, len(runes))
for i, r := range runes {
properRunes[i] = r
}
fmt.Println(Join(properNumbers))
fmt.Println(Join(properRunes))
}
Совершенно неудивительно, что это известная проблема, которая даже проблемой-то не считается. Это просто очередная забавная штука в Go, окей? Я очень рекоммендую прочесть соотв статью в Wiki по теме, станет ясно, почему трюк с передаванием []struct как []interface не прокатит. С другой стороны, подумайте об этом! Мы можем это делать, тут нет никакой магии, это просто проблема компилятора. Посмотрите, прямо здесь, чуть выше в коде я это сделал вручную. Почему компилятор Go не может заниматься этим шапито вместо меня? Да-да, явное лучше чем неявное, но все же?
Я просто не могу терпеть то, как люди смотрят на дерьмо вроде этого, которого в языке просто полно и продолжают говорить «ну да, это норм». Это не норм. Это то, что делает Go ужасным языком программирования.
Причина №5. Неочевидные циклы «по значению»
UPD: Это несколько надуманная проблема, если смотреть со стороны языка. А если не смотреть, то все равно, т.к. мы же говорим о косяках непосредственно языка, а не документации и экосистемы.
Это вообще первая проблема, с которой я когда-либо столкнулся в Go. Окей, у нас тут есть «for-range» цикл, который позволяет ходить по слайсам и слушать каналы. Его используют повсюду и это окей. Вот вам еще одна незначительная, но не очень приятная проблема, с которой сталкивается большинство неопытных неофитов: range-цикл поддерживает итерирование только «по значению», он просто копирует значения и ты ничего не можешь с этим поделать — это тебе не foreach из С++.
package main
import "fmt"
func main() {
numbers := []int{0, 1, 2, 3, 4}
for _, number := range numbers {
number++
}
fmt.Println(numbers) // [0 1 2 3 4]
for i, _ := range numbers {
numbers[i]++
}
fmt.Println(numbers) // [1 2 3 4 5]
}
Прошу отметить, я не ругаюсь на то, что в Go нет range-циклов «по ссылке», я ругаюсь на то, что эти range-циклы довольно неочевидны. Глагол “range” будто говорит “пройтись по элементам”, но не очень говорит “пройтись по копиям элементов”. Давайте посмотрим на секцию For из “Effective Go”, в ней ни слова про то, что range-циклы копируют значения слайса, там просто об этом не написано. Я согласен, что это очень незначительная проблема, к тому же я в свое время с ней очень быстро совладал (в течении минут), но неопытный гофер может потратить некоторое время на отладку кучка кода, не понимая, почему значения в массиве не меняются. Нужно было бы хоть немного, но обрисовать это дело в “Effective Go”.
Причина №6. Сомнительная строгость компилятора
Как я уже мог говорить ранее, Go считается чистым, простым и читабельным языком программирования со строгим компилятором. Например, у тебя не выйдет скомпилировать программу с unused import. Почему? Просто потому что Мистер Пайк считает, что так должно быть. Можешь верить, а можешь и нет, но unused import это далеко не конец Света и люди могут его пережить, особенно во время активной отладки. Я полностью согласен, что это не очень правильно и компилятор просто должно выводить какое-то предупреждение, но ради всего святого, какой смысл прекращать компиляцию из-за такой мелочи? Неиспользованный импорт, серьезно?
К Go1.5 нам подогнали классное изменение языка: наконец-то можно указывать элементы словаря без явного указания типа хранимого значения. Мальчикам из Go team понадобилось чуть более пяти лет, чтобы сообразить, что явное указание типа может быть чуток избыточным.
Другая замечательная штука, от которой я прусь в Go — это коммы. Видете ли, в Go у нас есть так называемые многострочные блоки объявления (import / var / const):
import (
"fmt"
"math"
"github.com/some_guy/fancy"
)
const (
One int = iota
Two
Three
)
var (
VarName int = 35
)
Окей, но как только дело доходит до «читабельности», Роб Пайк решает, что надо ВНЕЗАПНО добавить запятые. В какой-то момент после добавления запятых, он решает, что стоит оставлять запятую на последней строчке списка. Таким образом, вместо того, чтобы писать так:
numbers := []Object{
Object{"bla bla", 42}
Object("hahauha", 69}
}
Мы вынуждены писать так:
numbers := []Object{
Object{"bla bla", 42},
Object("hahauha", 69},
}
Я до сих пор задаюсь вопросом, почему мы не можем просто обойтись без запятых на блоках import / var / const и уж никак не можем на списках или словарях. В любом случае, Роб Пайк точно шарит лучше меня, так что все окей. Да здравствует Читабельность!
Причина №7. Кодогенерация в Go это просто костыль
Во-первых, я не имею ничего против кодогенерацию. Для бедного языка, вроде Go, это, возможно, единственный способ избежать копипасты для обобщенных штук. В любом случае, go:generate — тулза для кодогенерации в Go, которая используется гоферами по всему миру, просто отстой. Ну, чтобы уж быть до конца честным, тулза сама по себе ок, мне нравится. Проблема не в тулзе, а в самом подходе. Смотри, для того, чтобы сгенерить какой-то код, тебе нужно воспользоваться специальным магическим комментарием. Да, магическая последовательность байтов где-то в комментариях твоего кода может генерить код! Поглядите только, какое зрелищное шапито мы наблюдаем ниже:
func Blabla() {
// code...
}
//go:generate toolname -params -blabla
// code...
Комментарии должны объяснять код, не генерировать его. В любом случае, магические комментарии это обыкновенная рабочая практика в современном Go. Интересно, но всем все равно, всех все устраивает, это окей. Мне лично кажется, что это намного большее зло, чем чертовы unused imports, которые валят компиляцию.
Эпилог
Как вы видите, я не ругался по поводу обобщений / обработки ошибок / сахара и других классических проблемах, о которых говорят в контексте Go. Я согласен, что дженерики это вовсе не мастхев, но тогда дайте нам нормальную генерацию, а не волшебные циркопляски в комментариях, которые типа как генерируют код. Если в забираете у нас исключения, то пожалуйста, дайте нам возможность нормально сверять нулевые интерфейсы с нулем! А если вы у нас «неоднозначный в плане читабельности» сахар забираете, то дайте возможность писать код, который работает ожидаемо, без «гоп ца ца» сокрытия переменных.
Так или иначе, я продолжу использовать Go. На это есть веская причина: я люблю его. Я ненавижу язык: он просто отвратителен, но я обожаю сообщество, инструментарий и львиную долю концепций, которые предлагает язык, вообще всю экосистему.
Пссс, парень, не хочешь форкнуть Go?
divan0
Это самый нелепый список придуманных проблем, которые я видел. Возмущаться тем, что тебе неудобны запятые в литералах и называть отсутствие встроенного дорогого копирования слайсов разных типов — плохим дизайном, попуская Пайка — это новый уровень Хабра?
Илья, я знаю, что ты ещё учишься в школе, но зачем так позориться? Даже зная твою любовь к троллингу, это как-то слишком топорно.
namespace
И это необходимый минимум, чтобы вставить элемент в массив. Ты все еще считаешь, что Go совершенен?
evnuh
А кто сказал, что вставка элемента в середину непрерывного куска памяти — тривиальная задача?
lair
Вот только непонятно, почему она должна быть такой же сложной в коде.
evnuh
Ну потому что это 3 операции на самом деле, а не одна:
Но я согласен, можно было и сделать удобный метод вставки по позиции.
lair
Это технически три операции, а семантически — одна. Что мешало сделать
insert(numbers, i, v)
?divan0
Тем, что городить новый keyword ради спорной нужности операции — это именно то, от чего в Go старались уйти.
lair
К сожалению, вся остальная продемонстрированная работа со слайсами тоже не блещет.
divan0
Окей. Пишите на том, где вам блещет, к чему все эти сотни комментариев про «не блещет».
lair
Ну так, иногда хочется и с окружающими поделиться своими впечатлениями. Можно подумать, вы из других побуждений статьи пишете.
divan0
Конечно из других. Я делюсь знаниями в статьях. А вы в комментариях хейтите то, чем не пользуетесь. Это разные вещи.
creker
Почему бы не сделать это в виде библиотечных функций, как это везде повсюду? Как же знаменитый code-reuse? Из языка вырезали так много, что приходится обратно все туда руками заталкивать. И это будет не стандартный API, а велосипед, который все и каждый делают заново. Это — плохо продуманный язык. Кто-то слишком фанател идеей о «спорной нужности операции».
divan0
Приведите три реальных примера, когда Вам нужно засовывать элементы в произвольное место в массиве/слайсе. Реальные кейсы только, пожалуйста. У меня, к примеру, за последние 2 года таких кейсов не было.
Поскольку вы утверждаете, что это крайне нужная операция, и авторы Го просто недостаточно умны, в отличие от школьников, чтобы понять это, это не должно составить труда.
Zibx
Добавление элемента в сортированный список.
divan0
Если нужен сортированный список — используйте linked list и сортируйте при вставке, слайсы тут причем?
Еще примеры? Реальные, из ежедневных проектов, которые вы пишете, желательно.
Zibx
Часть алгоритмов сортировки. Те же деревья (не все они состоят из связанных списков), многие производят манипуляции с массивом в памяти, потому что всё равно всё упрётся в диск. Опять же, связанный список требует дополнительное место на хранение указателя на следующий объект. Массив быстрее связанного списка на ряде задач, например, взятие элемента i в случае массива — операция со сложностью 1, а в случае списка 1-N.
divan0
Хорошо, котируется — специфические структуры данных. Вопрос — как часто вы пишете свои структуры данных? Сколько раз в неделю? В месяц? В году?
Сколько процентов от вашего основного кода занимает разработка своих структур данных? Оригинальный вопрос в силе — приведите 3 реальных последних примера из своего опыта этой «высокой нужной операции». Вопрос без подвоха.
Zibx
Я пишу на js, но вы правы, это действительно редкая операция, последний раз использовал вставку в середину массива примерно в апреле, когда реализовывал те самые b+tree для серверсайда.
0xd34df00d
Много, я занимаюсь машинным обучением и написанием высокопроизводительных систем с этим делом. Месяц назад писал кусок, где нужно было именно это, например.
Zibx
Но относительно циклов или любой другой операции — вставка в середину действительно редка.
0xd34df00d
Что такое «вставка в середину относительно циклов»?
Kemet
старая изжеванная проблема — LinkedList vs ArrayList
Далеко не всегда связные списки хороши, к тому же в памяти гадят, а с учетом, что язык с со сборкой мусора…
barabanus
Забавно, что за более чем десять лет программирования мне ни разу не приходилось использовать linked list ни в геймдеве, ни в контестах, вообще нигде, кроме как в примерах первого учебника по ООП. Уж насколько медленная эта структура, учитывая структуру кэша памяти! Кстати, рекомендую к прочтению «What every programmer should know about memory»
0xd34df00d
Какая сложность нахождения элемента в сортированном linked list, меньшего или равного данному? Бинарным поиском-то уже не попрыгаешь.
Впрочем, зачем стартаперам с миллионами микросервисов думать о сложности, действительно. Надо больше статей про стартапы и простоту.
Извините за резкость, просто когда предлагают делать сортированные массивы на связных списках, это как-то перебор.
VolCh
Разнообразные отсортированные очереди структур данных. Добавление должно происходить не в конец очереди, а в место, определенное каким-то полем структуры, например таймстампом.
divan0
Придумать кейс можно, безусловно. Тут у меня те же вопросы: почему именно слайс и как часто вы пишете свои очереди структур данных? Речь о компромиссах и приоритетах. И да, все еще хочу увидеть по три реальных примера этого «частого важного кейса» из реальной практики.
VolCh
Я просто объединил свои частые кейсы. Реальные примеры, которыми я недавно занимался — очередь заявок на обработку оператором (заявки должны обрабатываться в сложном порядке, новая заявка далеко не всегда вставляется в конец очереди), очередь исполнения бизнес-транзакций по сущности (некоторые транзакции добавляются задним числом), очередь сообщений, пришедших на сокет, которые должны обрабатываться тоже не в порядке поступления. Трех достаточно?
Blooderst
Может пора задуматься над несколькими очередями с разными приоритетами вместо того, чтобы использовать очередь таким образом, что она перестают быть очередью?
grossws
Бывает логика приоритетов сложнее, чем в тривиальной priority queue. Взять, например, какой-нибудь deadline scheduling.
Blooderst
А почему сложная логика должна реализовываться посредством такого прямолинейного и простого инструмента как слайс, созданного для решения совершенно других кейсов? Для того, чтобы потом рассказывать какой это плохой инструмент?
VolCh
Очереди с разными приоритетами был второй этап развития после обычной очереди. В перспективе — очереди, динамически изменяющиеся, а не с определением места при вставке.
Blooderst
Да все понятно уже. Ваша бизнес логика давно переросла функционал слайсов, но вместо того чтобы это осознать и использовать другую структуру данных (связный список тут явно напрашивается) вы продолжаете есть кактус и утверждать, что использование слайсов для этой задачи оправданно и типично.
Ramires
Иногда можно уперется в память, т. к. в linked list хранятся указатели на предыдущий и следующий элементы.
И linked list не поддерживает случаный доступ ( привет, O(n) вместо O(1) )
Стоп, кажется, это уже было сказано здесь…
Blooderst
То есть возможность упереться в память при увеличении размера слайса вас не заботит (а это хоть и временное, но двукратное увеличение!) и вы не видите в этом проблемы, а вот пара лишних указателей (или даже один — списки бывают еще и односвязными, что в случае очереди кажется более оптимальным) — это большая проблема?
Blooderst
Да и тут речь даже не в том, какой способ лучше. Речь о том, что необходимость таких изощренных манипуляций по определению не может быть простой и не является типичным примером использования. В случае необходимости конечно придется повозиться, но это жизнь, так сказать. Но в 99% случаев при правильном подходе нет необходимости вставлять элементы в середину слайса и в этих 99% случаев слайсы в Пщ очень удобны и лаконичны.
Ramires
Не знал, что такая реализация ( это получается vector из C++ ). Тогда замечание про память невалидно.
Kemet
Такая проблема ArrayList'а есть, она известна, но на практике, бездумное использование ArrayList явно говорит о неверном дизайне. С другой стороны, раз выделив шматок памяти под слайс(с запасом, конечно), мы спокойно вставляем элемент в нужное место — происходит сдвиг элементов, без выделения памяти. Но когда нужен быстрый произвольный доступ — это вариант. Если же у нас миллион элементов в связном списке, то наложится и работа сборщика мусора и отсутствие произвольного доступа и тп.
Где-то лучше связный список, где-то массив, эта тема уже 100500 раз поднималась, и все достоинства и недостатки подходов давно известны. Уже не раз сталкивался с тем, что стараются избавиться от указателей и делать всё на массивах, ибо на но больших объемах сборщик мусора просто не дает нормально работать, что в C#, что в Go. Но спор то не об этом.
grossws
У связных списков есть другое проблемное место (особенно в concurrent окружении), они сильна трешат prefetch кэша, что может приводить к потере 1-2 порядков. Исключение — это выделение блоками (или одним потоком), чтобы все аллоцированные элементы связного списка лежали последовательно и давали предсказуемый memory access pattern.
mayorovp
Хм, а почему для таких очередей не использовалась пирамида?
0xd34df00d
Потому что профайлер показал, что так медленнее, или бегать по ней потом медленно.
Kemet
позаимствованный Oberon-Way не всегда единственно правильный.
creker
А кто сказал, что это сложная задача? Даже на С она решается тривиально и я даже осмелюсь сказать, что получится читабельнее этого месива из скобок и точек. Это не говоря о том, что в современных языках это обычно входит в стандартную библиотеку.
gurinderu
Я б написал так
И да, я тоже считаю, что это читабельнее.
mirrr
И чем оно читаемее, чем код ниже?
NeoCode
идеальный вариант ИМХО
defuz
Подброшу-ка я дровишек: именно так и выглядит аналогичный код на Rust. :)
gurinderu
Это же vec, а не array
defuz
А чем array, который вы имеете ввиду, отличается от Vec в Rust?
xytop
Написал на коленке вариант враппера для Go: play.golang.org/p/OgzWfYeB1V
Здесь нет проверок границ и многого другого, но все красиво обернуть при желании тут тоже можно.
Конечно другое дело, что все это ложится на плечи конечного программиста: либо писать реализацию самому, либо использовать готовые библиотеки.
defuz
Библиотека для вставки элемента в середину массива – это прекрасно! Сразу вспомнилось это:
xytop
Ну а что делать?
Люди требуют, чтобы было
А потом туда можно свистелок напихать еще разных.
В C++ std тоже включает много всего разного.
lair
Угу…
Компилируется (еще бы), но при запуске (очевидно), выдает:
xytop
Я же написал, что нету проверок никаких, это пример обертки.
Вы хотите production-ready код?
lair
Я хочу, чтобы проверки были compile-time, а не run-time. Это возможно?
xytop
В данном варианте нет.
lair
Вот это и проблема. А будет ошибка на Insert или на приведении — уже не так важно.
xytop
Согласен. В С++ это можно было бы решить шаблонами. А вот в С точно так же only runtime checking.
lair
А в некоторых языках просто есть дженерики.
xytop
Может и здесь скоро появится. «Некоторые» языки тоже не сразу их заимели.
lair
То-то нам (почти) в каждом посте про Го дают ссылку на этот же документ как на объяснение, почему в Го дженериков не будет и это правильно.
burjui
Андрей Александреску обмолвился о generics в Go в одном из CppCast:
«It became the n-word of Go. Like you can't say 'generics' with 'Go', because everybody is gonna be offended in Go communitiy»
https://www.reddit.com/r/programming/comments/3qs888/cppcast_d_with_andrei_alexandrescu
Для тех, кто не знает, «n-word» — это эвфемизм слова «nigger» в США.
tyomitch
<занудство>«n-word» — это не эвфемизм слова «nigger», а название слова «nigger». Эвфемизмы — например, «dark-skinned» — используются для обозначения негров, а не слов.</занудство>
burjui
Точно, спасибо за исправление.
gurinderu
mirrr
Поправьте меня если я не прав, но в вашем примере создаётся новый массив.
Это значит, что будет новое выделение памяти и будет дополнительная нагрузка на GC.
В некоторых приложениях это не приемлемо
defuz
Отличия в том, что это структура данных построенная на array, которая удобнее, но все же в памяти она занимает больше. В общем все тож самое что и в плюсовом std.
defuz
Тот факт, что мы активно обсуждаем операцию insert наталкивает меня на мысль о том, что видимо эти самые array в go являются growable (динамические). Если это так, то это мало чем отличается от векторов в C++ и Rust.
В каком языке векторы построены поверх array?
gurinderu
Скорее всего вы права, можно сравнить go array с rust vect.
В Java Vector реализован на Array, в С++ насколько я помню тоже.
grossws
В java Vector — давно устаревшее говно мамонта. А актуальный ArrayList работает поверх массивов, с динамическим адаптивным расширением в зависимости от размера данных. В openjdk8 этот массив может быть либо пустым (константой), либо размером не менее 10 элементов. При небольших размерах он увеличивается с шагом в 50% от текущего размера.
divan0
Я не мыслю категориями «совершенен»/«не совершенен», это оставим школьникам. Вставка в произвольное место — это by design дорогая операция, вынуждающая двигать память, но 99% кейсов использования слайсов — это append. Это называется «компромисс», и все что касается дизайна языка — это всегда о компромиссах. То, что в слайсах в Go так неудобно делать произвольную вставку — создает стимул думать головой о том, какая структура данных больше подойдет, если нужно делать много быстрых произвольных вставок, а не вслепую ворочать память.
В любом случае, школьник тыкающий в Пайка и Томпсона, «объясняющий» им своим невежством, какой плохой они придумали дизайн — это мне непонятно.
namespace
Ваня, ты наверное про нигилизм не слышал. Я не спорю, что Пайк и Томпсон крутые чуваки, но давай они хоть как-то будут объяснять свои решения. Удел разумного человека не идти на поводу у авторитетов, а делать соотв умозаключения, которые приведут его к истине.
KReal
Что вы заладили — «школьник», «школьник»… у вас школьник пирожок отобрал, что ли?
andyN
Поддерживаю. Если автор статьи и учится в школе, то это совершенно неважно — он в свои годы уже намного умнее и рассудительнее чем половина моих бывших одногруппников.
alterpub
У вас судя по всему какой-то комплекс, на тему школьников, лол ;)
yul
Меня больше всего поразило, что нельзя определить метод для этого. А еще кто-то недавно гнал на Ruby с его monkey-patching…
mirrr
Я не совсем в теме, почему нельзя?
kekekeks
Дженериков нет. Этот метод придётся копипастить для каждого типа, используемого в массивах.
mirrr
А что мешает использовать интерфейсы?
kekekeks
Необходимо определить операцию не над определённым типом, а над массивом таких типов, интерфейсы здесь причём?
andrewnester
есть способы с использованием стандартного пакета «reflect», но это конечно не очень круто, дженериков не хватает реально
kekekeks
Рефлексия — это не «не очень круто», это «очень не быстро».
mirrr
Я об []interface.
Если существует такая частая необходимость вставки в вашей структуре данных, да еще со множеством различных типов, то почему не сделать тип-обертку над []interface{}
lair
Потому что преимущества строгой типизации теряются.
defuz
Э… Всмысле нельзя? Т.е. вообще нельзя? А как жить, если надо? Можете для человека, который вообще не знает Go, коротко объяснить что именно нельзя и почему?
mayorovp
Нельзя определить метод insert, который будет работать с любым массивом. Потому что массив целых чисел и массив строк — это разные типы данных, один метод не может работать с ними обоими.
mirrr
Может. Но есть обратная сторона медали, как описали ниже. Невозможно проверить тип во время компиляции, например.
taliban
Ты ведь в курсе что можно оборачивать код который тебе не нравится в функции с более красивым дизайном? Го упрощен до невозможности, автор не ставил целью чтоб ты кончал когда пишешь на го, он упростил все действия, чтоб увеличить простоту и скорость, добавляя безопасности. Ты пишешь на голом языке, но избавлен от слежки за кодом постоянным. Со временем появятся и конструкции более менее универсальные, но сейчас цель у авторов отнюдь не идеальная красота кода, и они прямо об этом пишут.
lair
О, а как в Go обернуть в функцию код для вставки элемента в середину слайса произвольного типа (с сохранением типобезопасности, конечно)?
mirrr
Функция не идеальна, но я без малого три дня как начал вникать в go, может более опытные могут сделать лучше? То же можно сделать и в обертке, которую я приводил выше, там был ваш вопрос о типизации.
lair
Во-первых, типобезопасность не сохранена — если я передам
s
одного типа, аx
— другого, будет ошибка приведения.Во-вторых, не будет работать для произвольного (неизвестного на момент написания функции) типа.
Blooderst
В Go УЖЕ есть универсальная встроенная функция манипулирования слайсами, которая работает с любыми типами и с максимальной скоростью — append. И не нужно ничего изобретать, все уже изобретено и реализовано. Если вам не нравится синтаксис — это ни как не проблемы языка. Более того, так и задумано. Неужели вы считаете, что авторы оказались не способны привнести в синтаксис сахарку, если бы посчитали это необходимым? Я вас уверяю, смогли бы. Тот факт, что эта функция реализована именно таким образом говорит о том, что авторы языка считают именно такой синтаксис наиболее подходящим и я лично с ними в этом солидарен. Этот синтаксис показывает программисту, что операция которую он совершает, не простое присваивание, а ресурсоемкая задача. Нравится вам это или нет, но этот язык именно такой. Если он вам не подходит — не пишите на нем. Но не надо говорить, что язык плох из-за того, что он для вас недостаточно подслащен. Расслабьтесь и наслаждайтесь жизнью в сторонке от Go.
lair
Спасибо за просвещение.
Теперь поднимитесь на пару уровней выше по ветке и посмотрите, на какой именно комментарий я отвечал.
creker
Ох е. И вы действительно в это верите? Есть другая причина — разработчики языка не продумали эту сторону, и мы имеем неоптимальное, некрасивое, неудобное решение. И ваши оправдания тому, что это нормально, выглядят нелепо. Ресурсоемкость задачи программисту должна говорить документация, но никак не монструозный синтаксис, который вообще никак с ресурсоемкостью не коррелирует и не связан. Есть плохой синтаксис и есть примитивная операция как то вставка в произвольное место массива. Это примитивная операция и не надо пытаться доказать обратно в надежде, что это сойдет за оправдание Go.
А давайте вы лучше будете жить спокойнее в сторонке от тем, который так вас подрывают. Нравится — ок. Нам не нравится — извольте нам дать свободу для обсуждения. На любой язык ругаются и это позволяет сделать их лучше. На Страуструпа с плюсами жалуются — получаем новые стандарты. На команду C# жалуемся — получаем новые версии языка. Авось и до команды Go дойдет, что дизайн языка таки не блещет. В нем много хорошего, но достаточно проблемных мест, которые никак не подорвут его философию.
mirrr
Точно, не заметил.
Задача слишком быстро начала обрастать дополнительными условиями) Началось все с «невозможности» обернуть код вставки выше в метод. Дальше понадобилась одна ф-я для нескольких типов, чтобы не «копипастить» метод, потом строгая типизация, далее произвольные типы и типобезопасность.
Думаю остановлюсь на варианте со строгой типизацией и возможностью реализации для каждого типа, для которого понадобится использование функции в приложении.
taliban
Отвечу Вашими же словами :)
У человека выше подгорело из-за синтаксиса
А не из-за того что в go нет шаблонов. Как бы тоже следите за цепью диалога, а потом свои несчастные 5 коп вставляйте.
lair
Вот прямо в исходном посте все про это написано: «А так как у нас нет никаких этих ваших дженериков, то написать красивую функцию insert(), которая будет прятать весь этот ужас просто не получится.»
Для меня это очень логичный ход мысли: уродливый синтаксис — написать что-нибудь, что его прячет — что, мне это писать для каждого типа?! Причем это не выдуманное, я по этой тропинке прыгал во времена .net 1, где напишешь какую-нибудь удобную мулечку, а потом ррраз — и вынужден ее повторять еще для пары разных типов.
(причем даже когда дженерики случились, тоже было больно, потому что операции сложения они, скажем, не покрывали, и приходилось отдельно прыгать)
taliban
Да, придется повторять, с этим то никто и не спорит, но я не пойму, почему не сказать прямо (я напомню я отвечал не напрямую посту, а комментарию) «меня бесит копипаст, не хочу повторять код». Вместо этого приводят просто длинный и уродливый кусок кода со словами «смотри какой уродливый код мне приходится писать?». Об этом речь а не о типе и невозможности написать универсальный для типа код.
lair
Это самоподдерживающаяся аргументация. Копипасты приходится писать, потому что язык не позволяет сделать универсальное решение; универсальное решение нужно потому, что кусок кода, решающий задачу, уродлив. Выдерните любой пункт — остальная боль тоже пропадет, потому что можно будет одно за другим спрятать.
taliban
Нет, это называется вброс, или «непрямой аргумент». Когда автор специально искажает данные для пущего эффекта. Ибо прямые указания будут иметь меньший эффект. Если я скажу «не люблю копипастить, были бы дженерики тут», это все поддержат, ибо никто этого не любит, это очевидная проблема, и никто даже обсуждать это не будет, про нее все знают, но если я скажу «язык уродлив и не позволяет мне делать мои элегантные конструкции, которые давно уже есть в других языках». Вроде смысл тот же, но бомбить начинает у людей уже на много сильней. Я не приветствую такие провокационные фразы от других.
lair
Ох, я был бы рад, если бы это было так.
taliban
В том то и дело, в среде любителей го этот вопрос обсуждается больше всего, это очень наболевшая тема и все были бы счастливы если бы эта фича наконец-то появилась. Я даже не знаю кто может отрицать что в го этого не хватает. Как и адекватной модульной системы. Я очень сомневаюсь что адекватный человек, знающий GO будет спорить про эти две вещи.
mird
А потом придет divan0 и скажет, что создатели языка все уже придумали и дженерики не нужны, а этой задачи вообще не существует.
creker
Только вот я это читаю и просто диву даюсь — да это ж пересказ всего того мата, который был у меня, когда я начинал писать на Go. Простая примитивная программа из-за тех же слайсов и чехорды между [n]int и []int превращается в такое убожество, что страшно. Язык простой, да, но код на нем получается совсем не простой.
hurricup
Во-первых, зачем же ограничиваться обсуждениями школьник/не школьник, давайте обсудим национальность и сексуальные предпочтения автора поста.
Во-вторых, это перевод.
В-третьих, если у языка с точки зрения программиста есть недостатки, почему бы ими не поделиться? Если это будет субъективная неприязнь данного программиста, статья сгинет в Лету, если многих это беспокоит, почему не обсудить? Спокойно и конструктивно. После ваших комментов возникат одна мысль — «Бомбануло».
AterCattus
Это авторский перевод, кстати.
stepanp
Судя по традиционному срачу в коментах, Go это новый JS
evnuh
Ну окей, человеку не нравятся эти моменты языка, он это рассказал в своём блоге. Но зачем мне-то знать, что ему не нравится?
evnuh
Прошу заметить как автор элегантно в первом предложении снял с себя ответственность под видом перевода. А перевод-то своего собственного поста :)
evnuh
Решил покопаться ещё. Автор, опять же, сам лично разместил ссылку на реддите на свой же пост: www.reddit.com/r/programming/comments/3qjo3y/why_go_is_a_poorly_designed_language_from_a. И «обширность» обсуждения на реддите спровоцирована не статьёй, а ложными фактами, отсебятиной и враньём в цитатах Роба Пайка у автора (люди потрудились, поискали и выяснили, что он такого не говорил).
В общем, не стоит попадаться на грязные уловки автора и мнимый авторитет самого себя :)
namespace
У меня в статье нет ни одной цитаты Пайка, о чем ты?
evnuh
Первое предложение третьего абзаца в оригинальной английской статье. Пайк такого не говорил и даже не подразумевал.
xytop
Он говорил как-то так:
namespace
Ну смотри, там я действительно облажался — я не хотел цитаты, я хотел его несколько перефразировать. Там была ссылка на его статью, где он после списка вещей (огромного списка), которые они из Go убрали, говорит: «But you can't take out everything. You need building blocks such as an idea about how types behave, and syntax that works well in practice, and some ineffable thing that makes libraries interoperate well.»
Это кагбэ намекаэт, что он пытался убрать все по-максимуму, так что я не очень-то и соврал. С другой стороны — да, нехорошо получилось. Но ничего, я кавычки из оригинала убрал и заменил said на according to, спасибо за фидбэк.
jemali_m
Ооо, началось!
jemali_m
Ну давай, расскажи мне )
jemali_m
ЕЩЕ!!!
leventov
Стиль неуместный.
namespace
Простите, мистер зануда, я больше так не буду.
leventov
Зря вы считаете, что ваши фамильярные выпады на Пайка и закос под нейтив инглиш спикера в оригинале — это сколь нибудь оригинально или остроумно.
namespace
Я тебе даже больше скажу: мало того, что я так считаю, я глубоко убежден, что мои выпады на Пайка и украшения в английским — это очень оригинально и обосрацца как остроумно! Кстати, зашел к тебе в профиль минус поставить, а там уже… И заметки нет — не знаю, за что. Не напомнишь?
Utter_step
Ну так-то зачем?.. Всё впечатление об адекватности теряется сразу.
NeoCode
Нормальный стиль. Главное ведь не стиль, а чтобы информация усваивалась мозгом максимально быстро. Можно писать скучную документацию в официальном стиле, а можно вот так. Какой вариант усвоится лучше?
leventov
Быстрее усвоится то, что не отвлекает на форму, если уж на то пошло.
QtRoS
Для меня лично некоторым гарантом юзабельности Go является его использование в том же ВК (статьи были на Хабре). Но чему-то слишком разрекламированному у некоторых людей часто возникает отторжение.
Да простит меня хабр, но именно так произошло, например, с фильмом «50 оттенков серого» — чрезмерная реклама и разочарование от обыденности.Статья — наглядный пример такой реакции.hurricup
Это может ничего не значить. Допустим, у меня есть нагруженный проект. И нужно поправить некий батлнек производительности, максимально оптимизировать. И предположим, что в силу сложившихся обстоятельств наилучшую производительнсть мне дает Brainfuck (обеспечивая необходимую стабильность и не порождая других проблем). Конечно же, я напишу это на нем. Но это не говорит ничего о юзабельности языка. Лишь о том, что у него есть свои сильные стороны и что ситуативно имеет смысл его использовать.
И это лишь один из вариантов, почему язык %language% используется в %project%.
AterCattus
Во да, мы Go выбрали именно для тех задач, где он проявляет свои сильные стороны.
QtRoS
Ну да, значит язык применим на практике (пусть даже в определенном круге задач), то бишь юзабелен. Я особо ничего больше и не имел ввиду. Если не ошибаюсь, то как раз AterCattus рассказывал в презентации про то, как хорошо Go был применим для задачи, подчеркивая отсутствие желания переписывать всю логику сайта на этом языке. Полностью согласен.
AterCattus
Конечно бесмысленно пытаться переписывать то, что и так хорошо работает. Go для тех задач, где его особенности проявляют себя как сильные стороны по сравнению с тем же C/C++.
c4boomb
Вообще считать ВК авторитетным глупо. Они не использовали ООП при написание такого большого, сложного по структуре и высоконагруженого проекта.
kyrie
Если команда ВК смогла написать проект такого уровня без использования ООП, то я скорее задумаюсь о необходимости ООП, чем о их криворукости. Я никогда не работал с ВК кроме как пользователь, но мне он не показался бажным или неповоротливым.
Bringoff
<offtop>
Давненько вы им не пользовались :)
</offtop>
Bringoff
Человек, поставивший минус, видимо, никогда не пытался смотреть видео или гифки вконтакте.
Alesh
Не могли бы развить свою мысль, а то не совсем понятно что имели ввиду.
Bringoff
Ну, гифки грузятся медленнее, чем видео на ютубе, а видео вконтакте вообще в половине случаев не проигрывается. И проблеме этой не один год, насколько я помню.
mayorovp
Пятая проблема надумана — я не слышал ни про один язык программирования, где были бы циклы foreach «по ссылке» (кроме, наверное, C++ — там я немного отстал от мейнстрима).
salas
Аналогичный код на Swift не компилируется: «cannot pass immutable value to mutating operator: 'number' is a 'let' constant». Кажется, автор этого просит, а не циклов по ссылке.
salas
Ха. Просто так не компилируется? Да кого это останавливало!
Никогда ещё каменты в интернете не отзывались мне так быстро: оказывается, умудрился неделю назад посадить баг именно на Swift и именно этим способом.
lega
Хотел про это же написать, но автор (частично) прав, например если перебирать массив объектов (в питоне), то эти объекты можно будет изменять, в го они копируются, пример.
mayorovp
В питоне объекты всегда передаются по ссылке на них, в го — всегда копируются. Циклы тут ни при чем: play.golang.org/p/B5Zyjj1-R3
salas
Но, кстати, приведённый wtf переводится на Python без потерь:
lega
Потому что в питоне (и в некоторых других) простые типы как строки и числа копируются, а словари, массивы и т.п. передаются ссылкой (т.е. копируется ссылка на объект).
salas
Нет. Всё ссылка на объект, а вот += может быть вызовом метода __iadd__, а может быть присваиванием этой ссылке нового значения, если у объекта не оказалось этого метода.
mayorovp
Вот, кстати, доказательство:
PS извиняюсь за минус, не сразу понял о чем вообще речь
funca
Конкретно с мелкими int тут другая история. В классическом CPython целочисленные значения от -5 до 256 представляются ссылками на массив констант. Поэтому переменные с одинаковыми значениями в диапазоне [-5, 256] содержат одинаковые ссылки.
Для остальных целочисленных значений такое условие уже не выполняется:
Эта особенность поддерживается на уровне операций:
ZyXI
В примере mayorovp ничего не изменится, если вы замените
a = 2
наa = 2.0
. Данная оптимизация тут совершенно не причём: это иллюстрация к тому, чтоfoo += bar
естьfoo = foo + bar
, если уfoo
нет метода__iadd__
(в этом случае это будетfoo.__iadd__(bar)
). Ни у строк, ни у чисел в Python такого нет.ZyXI
Если вам нужен адрес объекта, то вместо
object.__repr__(obj)
можно использоватьid(obj)
: он быстрее печатается. Правда, не показывает тип, но здесь тип очевиден.VolCh
PHP.
mayorovp
Да, точно…
ik62
в D программист управляет типом цикла(который может быть и по ссылке и по значению):
foreach(ref x; y) {
…
}
foreach(x; y) {
…
}
bo0rsh201
я тут на сцене появляется PHP как пример «совершенного» языка программирования, где это возможно :)
P.S. опоздал с комментом про PHP
namespace
Это можно сделать как минимум в C++ и D. Я уверен, что в доброй пачке других языков аналогичное поведение также возможно. Конкретно в данном случае автора смущает не то, нет циклов «по ссылке», а то, что это никак не освещенно в документации.
mayorovp
Довольно странно освещать в документации отсутствие некоторой фичи…
6opoDuJIo
… но вполне нормально детально изобразить поведение цикла.
VolCh
Никто не просит писать в документации по циклу «цикла по коллекции с передачей элемента по ссылке в Гоу нет», просят написать «цикл по коллекции в Гоу всегда передаёт элемент по значению».
mayorovp
Цитата из спецификации языка:
assigns — это присваивание. Присваивание — это всегда копия.
grossws
Только, если ранее дано такое определение assign. Т. к. присваивание часто подразумевает присваивание ссылочного типа.
grossws
У автора просто неудачный пример с int. Если взять что-то вида
play.golang.org/p/HCBcUB1Z6yИ в подавляющем большинстве распространенных языков (C, C++, Java, Scala, Python, Ruby, JS, как минимум) поведение будет отличаться от Go, т. к. поле в структуре поменяет значение.
grossws
Насчёт C и C++ погорячился, структура может. В смысле цикла foreach. А c for всё и так очевидно.
hurricup
Perl5
s_kozlov
Эта проблема приятно решена в PHP, например. Добавляешь & и все.
Почему бы так не сделать в Go, удобно же.
namespace
Да, аналогично в С++ и по-моему, в D.
s_kozlov
P.S. Тоже опоздал про PHP…
VolCh
В случае массивов объектов они всегда передаются по ссылке (что логично). & нужно ставить только для изменения элементов массивов скаляров (неоднозначно логично или нет) и массивов массивов (не логично).
s_kozlov
Для Go, по аналогии с PHP, я бы предложил так:
или так, более согласованно
что будет происходить, если arr — слайс интерфейсов или каналов, оставим пока за скобками
dbelka
Ещё не понятно почему нельзя было сделать нормальный(!) менеджер зависимостей, ведь есть куча успешных реализаций: npm(nodejs), cargo(rust), composer(php),…
avdept
Если бы хоть немного погуглили перед тем как писать коммент, то все нашлось бы
github.com/avelino/awesome-go#package-management
namespace
Много кому (в том числе и мне) нравится go-get, прямо очень-очень нравится. Но так как мейнтейнеры (в итом числе и я) иногда ломают пакеты это не очень класс. Хотелось бы, чтобы была возможность задавать зависимость не только по URL, но еще и по git тегу, как это делает labix.org/gopkg.in
bolk
В мире Гоу это делается так: форкается пакет и зависимость прописывается от него.
Ivanhoe
А с транзитивными зависимостями как? Ну т.е. форкнутый-то пакет тоже от чего-то зависит.
Morthan
Меня настолько заинтересовали слова «шапито» и «циркопляска», что я даже не поленился посмотреть, как оно в оригинале. Потом поискал, сколько раз эти слова употреблялись на хабре ранее. Много думал.
Zagrebelion
если заглянуть в спецификацию и прочитать, что «a:=42» — это сокращённая запись выражения «var a=42» то третий пример (со скрытыми переменными) перестанет быть таким уж неожиданным.
namespace
Да, это так. Автор же и написал, что по спеке это совершенно легитимная тема, но так как слева err, можно легко ошибиться и попасть вот в такую скополовушку. В принципе, это не критично и любой толковый гофер быстро прохавает тему, но все равно указывает на несссовершенство языка.
gunya
Этот пример вообще дико странный, потому что является наглядной демонстрации областей видимости.
В C++ все точно также, один в один. Если ты открываешь блок { }, то значит, определяешь новую область видимости, значит, если ты определяешь новую переменную — старая не перезапишется. В чем несовершенство языка, скажите мне, пожалуйста?
defuz
Наверное все-таки в том, что определние новой переменной и присваивание нового значения визуально мало чем отличаются (привет опечаткам и долгому дебагу).
shuler
Вы кажется не осознали всю боль. Откройте пример play.golang.org/p/TNfS7eyYSf и сотрите фигурные скобки, которые задают новую область видимости.
Код скомпилируется, несмотря на повторное :=
А вот если еще объявить var err error перед этим, то получим уже
no new variables on left side of :=
gunya
Окей, я осознал всю боль. Доработал пример, чтобы все стало еще больнее play.golang.org/p/U4Xm5dDf52
nwalker
Вот этот пример прямо шикарный вышел.
grossws
Лишнее двоеточие в 24 строке. Оно же, поправленное: play.golang.org/p/T6_6naVdv6
gunya
Я его намеренно оставил, чтобы сразу огрести проблем на, казалось бы, синтаксически корректном примере.
C++ в такой ситуации гораздо понятнее бьет по рукам с ошибкой «redeclaration of 'int a'», но в плюсах нет оператора множественного присваивания.
grossws
Понятно. Я думал, что вы хотели показать проблему с
9mm
В том-же D, shadowing запрещён, что, на мой взгляд, добро. — сам я не редко совершаю ошибки связанные с ним.
Daniro_San
Пожалуй скажу немного.
Go действительно поначалу очень привлекает.
Меня привлек распиаренностью где бы то ни было, воплями о «языке нового поколения», сочетании (!) Си и паскалеобразного синтаксиса.
Действительно, операция ":=" выглядела для меня странно…
Как и объявления типов справа — показалось слишком громоздким.
Потом становится понятно что это просто очередная пустышка.
Давайте ка вспомним ту самую цепочку «инновационных языков» зарождавшихся по очереди в недрах Apple для замены Objective-C.
Припоминаете?
Неужели сейчас началась та же болезнь у немного другой компании?
andrewnester
объявление типов справа кажется громоздким, а слева вполне лаконичное? кол-во символов то одинаковое
Daniro_San
Вот пример с Go:
А вот с C++:
У вас есть возможность посчитать символы и подтвердить свои слова.
andrewnester
ну вы же сказали именно про типы, а не переменные
Пример на Go
Пример на C
По-моему, в плане громоздкости нет разницы. Может быть Вам непривычно, это да
Но с переменными вы правы, но это уже к синтаксису перменных, а не к типам
Daniro_San
Выходит, моя ошибка.
Я как раз имел ввиду объявление типа переменной.
NeoCode
Слово «var» добавляется.
Иногда говорят что так проще для парсера, потому что «var» — заранее известное ключевео слово, после которого может быть только объявление объектов; а «int» — имя типа, которое в общем случае может быть и пользовательским именем типа. В общем это правильно (для функций однозначно правильно иметь ключевое слово «func», «fn» и т.п. в начале), хотя при желании можно сохранить и оригинальный сишный синтаксис для переменных — так как переменные объявляются чаще всего, и для сохранения единообразия с объявлением полей структур.
Для этого нужно принять что любые конструкции вида «name1 name2» (два идентификатора подряд) это всегда объявление объекта name2 типа name1. Все остальные случаи сделать как-то по другому. В частности, запретить заключать объявляемые объекты в круглые скобки, как это возможно в С/С++.
mirrr
Еще упрощает задачу программисту в поиске мест объявления переменных, всего-то var и :=
NeoCode
Нет, аргументы функций внутри самих функций это тоже объявление переменных. Но идея про легкость поиска мне понравилась. В известных мне IDE зачастую не хватает умного поиска — найти все места где переменная объявлена, где она читается, где изменяется и т.д. Аналогично с типами — где объявлен, где объявляются объекты этого типа, где наследование от этого типа…
Aleksi
Вот тут подробно написано: blog.golang.org/gos-declaration-syntax
NeoCode
Опрератор := сам по себе очень неплох (я восхитился лаконичности и красоте, а также тем как удачно вернули к жизни это старое доброе паскалевское сочетание символов), но реализация ИМХО не совсем правильная. Это оператор, а не замена инструкции объявления, поэтому у меня большие сомнения в выражениях вида a, b := foo(). Неясен статус запятой — это оператор или что? Какой у нее приоритет по отношению к оператору создания объекта? Сейчас как-то не модно стало точное описание всех синтаксических элементов языка, а жаль. Просто показывают кучку примеров — смотрите как просто писать программы — тяп ляп и готово! Вместо того чтобы дать исчерпывающий перечень всех групп синтаксических элементов, их смысл, состав и правила использования.
У него есть еще одна странность — он не работает внутри выражений. То есть x := (y:=a+b)*c не прокатит, а жаль, это было бы действительно по-хакерски.
Daniro_San
Кому то очень нравится Go?
Или я в чем то не прав?
Ответь мне, о ты, минусовавший!
kuznetsovin
А что не так, не могли бы вы пояснить? По мне приведенный код вполне очевидно работает…
На сколько я понимаю это чем-то похоже:
Так что ничего не очевидного я тут не вижу
namespace
В твоем коде очевидно, что `a` — это новая переменная. В коде из топика используется :=, слева от которого новая переменная err. К тому же там контекст может быть еще слабее, чем вложенная функция — цикл или условие. Короче говоря, как-то не очень на руку «читабельному» языку.
VolCh
Как минимум для джаваскриптеров не очевидно.
MuLLtiQ
В JavaScript все проще, там если
var
или (что еще лучше)let
— значит новая переменная, если нет — берем из внешней области видимости. Ну т.е.Кстати, лично для меня, по сравнению с тем же Питоном, это гораздо удобнее.
andrewnester
вместо def up напишите if
а вообще тут дело в := и это то как он ведет себя согласно документации
другое дело, что программист, пишущий/читающий код, может ожидать от этого оператора немного другого
VolCh
Главное, что может легко спутать с = чисто визуально. Может точно знать особенности := и = в Гоу, но просто спутать.
semenyakinVS
Автор-переводчик, что вы наделали! В статье творится просто невероятная понжовщина, и это ещё плюсовики серьёзно не ворвались.
А статья любопытная. Из неё понял, что у Go есть определённое родство с Питоном (те же слайсы и механизм наследования в чём-то похожий).
Как убеждённый фанат плюсов, я, вроде бы, должен радоваться кривулькам конкурентов. Но скажу что думаю: Go просто относительно молодой язык, который ещё успеет отшлифоваться в процессе различных процессов.
namespace
Довольно спорное утверждение. Понимаете, Пайк и компания ясно дали всем еще в прошлом году понять, что «язык стабилен и мы не планируем его менять». С каждым релизом языку уделяют все меньше и меньше внимания. Это не потому что он уже взрослый и достиг своего технологического апогея, а просто потому что Пайка все устраивает, а он вообще считает, что подстветка кода не нужна, а лучший редактор это acme
semenyakinVS
Если так, то печально. Неужели на Go уже есть достаточное количество написанного кода, чтобы говорить об откатанности его фич?
М-да… А чем подсветка-то им не угодила?
namespace
По-моему сейчас вообще лучшее время, чтобы Go форкнуть и сделать язык без старперов.
Не знаю. Но скорее всего как обычно — отвлекает и нарушает читабельность кода.
mickvav
Ну, если только на слайсы смотреть, то они ещё в фортране были. ;) Причём синтаксис там в этом месте куда прямее — www.mathcs.emory.edu/~cheung/Courses/561/Syllabus/6-Fortran/array4.html
0xd34df00d
А что тут врываться-то.
semenyakinVS
Ну, холливара по поводу «С++ vs Go» не развернулся в достаточной степени… И слава Богу!
П.С.: Хотя в статье-ответке на эту статью по поводу плюсов всё-таки развернулся срач. Вообще, нужно сделать мега-статью, в которой было бы максимально объективное сравнение языков по основным метрикам: скорость работы, лаконичность кода, выразительность (хотя выразительность это спорно), и.т.д.
tyomitch
Вообще, нужно сделать мега-статью, в которой было бы максимально объективное сравнение молотка, дрели, паяльника и напильника по основным метрикам: скорость работы, компактность, мощность и т.д.
semenyakinVS
Я имел в виду сравнение языков, которые имеет смысл сравнивать — чтобы можно было тыкать любителей похолливарить в эту статью и не тратить сотню комментов на вырывание клоков волос друг другу в бессмысленных спорах.
faost
Пункты 3 и 5 о чем вообще? Из опыта работы с другими языками программирования для меня это очевидное поведение. Просто для tucnak, исходя из его опыта, это непривычно.
Find_the_truth
Есть язык программирования (в данном случае GO), есть те, кому он нравится, есть те, кому не нравится. К чему устраивать тут дискуссии по поводу «это я напишу с трех строк» — «а я напишу это в одну строку». Каждый выбирает то, что ему нравится, к чему он привык.
NeoCode
Такие дискуссии очень интересны. Сравните хотя-бы количество комментариев к этой статье и к любой другой.
Мне как человеку интересующемуся дизайном языков программирования и разрабатывающему свой язык, интересно в особенности.
misha_shar53
Я тоже пытаюсь написать свой язык программирования. Если можете вышлите спецификацию своего языка и комментарии на misha_shar53@mail.ru. Хотелось бы ознакомиться.
6opoDuJIo
К тому, что Go интенсивно форсят, почём зря.
Suvitruf
Почему зря? Из-за того, что его так форсят, постоянно появляется куча новых статей и обсуждений, из которых можно вынести много полезного.
6opoDuJIo
«Из-за того, что хлеб намазывают маслом, он становится покрыт маслом».
Нет, серьёзно, фраза так и выглядит.
Или вы думаете что он станет лучше только из-за того, что его будут больше обсуждать и придумают больше путей обхода недостатков дизайна и ограничений?
Suvitruf
Я разве говорил, что «он станет лучше»? Нет. Из комментариев к статье, к примеру, много интересных вещей узнал, даже не связанных с Go.
VolCh
Есть те, кто к нему (и конкурентам) присматривается для решения в будущем каких-то задач и ещё окончательное решение не принял.
webmasterx
а какая подобные операции из примера 1 выглядели бы в Rust?
dbelka
Как-то так:
namespace
>Какие дураки Rust придумывали… Другое дело Пайк и Томпсон, они-то знают, что вставка в непрерывный кусок памяти это сложная задача!
zagayevskiy
Какой «громкий» язык. Зачем везде этот восклицательный знак?
dbelka
«!» вместо мата :) Если серьезно, так обозначаются макросы в Rust, мощная возможность для расширения языка и сокращения шаблонного кода. Вот тут можно почитать kgv.github.io/rust_book_ru/src/macros.html
Googolplex
Хочу ещё добавить, что разделение «массивов» на owned vectors, которые, собственно, являются владельцами куска памяти, и срезов, которые просто предоставляют окно в уже существующий кусок памяти (естественным образом вытекающее из концепций владения и заимствования), на мой взгляд, удобнее и понятнее в работе, чем модель, когда сами срезы владеют памятью, на которую указывают.
NeoCode
Еще интересные мысли по слайсам. В D применяется специальный оператор $, который означает размер того массива, в контексте которого он применяется. То есть arr[$-1] это как раз последний элемент массива. Похоже чем-то на отрицательные индексы Питона, но пожалуй более строго… ведь индекс может получиться и в результате вычисления, вызова функции, и кто знает какой он там получится?
namespace
Звучит как очень правильное решение, которое можно спарировать фанбою Go—адепту «читабельности». Но ты не волнуйся, Ваня все равно выкатит что-то вроде того, что $ это большое зло и вводить его в язык это гигантская трагедия, лучше заниматься циркоплясками с длиной контейнера. А вообще я его в PHP видел этот знак, нам это точно не надо!
VolCh
А ещё он в баше активно используется. Ещё перл на ум приходит.
NeoCode
Тут еще есть такой аспект: операторных символов в ASCII мало (а использовать Unicode непреемлемо по причине того, что символы должны гарантированно быть на стандартной клавиатуре в любой стране мира). Поэтому при разработке языка нужно расходовать операторные символы достаточно продуманно.
0xd34df00d
И в хаскеле, да.
Но там вообще много значков и их комбинаций. И, что самое прекрасное, большинство из них не прошито в ядро языка, а определено вполне библиотечным образом.
ik62
Более того, его можно оверлоадить dlang.org/operatoroverloading.html#dollar
bolk
> Мне особенно нравится это шапито с троеточиями…
Троеточие это вот: ? (triple colon), «…» — многоточие.
defuz
И не многоточие, а три точки (в исходном коде Go).
</занудство>
nikitadanilov
> Да-да, явное лучше чем неявное, но все же?
Наверное проблема в том, что использование []FancyInt как []Stringy некорректно?
Иначе в Join можно было бы сделать
У массивов нет нетривиального subtyping-а про типу элемента, это вроде как общеизвестно.
Daniro_San
Мне нравится это бурное обсуждение.
Впрочем, как и все обсуждения Go.
Хомячки (и не только на картинках) бесятся.
Неплохо расслабляет после работы с C# кодом.
ZOXEXIVO
Взглянув на код, сразу понял что язык мне никогда не понравится
trong
Складывается впечатление, что автор статьи не вполне понимает, что при всех достоинствах и недостатках Go — это все же нишевый язык. У компании в какой-то момент остро встал вопрос — где брать высококласных разработчиков в требуемых количествах? Пока тебе требуется 1-2-5 — это сложно, но реализуемо. Если тебе требуется тысяча — это практически неразрешимая задача. В итоге Google пошел альтернативным путем — разработал язык на которой можно быстро (пере)обучить человека с минимальным бэкграундом и при этом не дать возможности этому новообученному специалисту простора для выстреливания в ногу.
Если вы рассчитываете на проект, который будет писаться в течении 10-15 лет, то вы должны понимать, что команда может смениться и даже не раз и вам нужно, чтобы код был прост и понятен, чтобы в нем не было немыслимых заковырок, определяемых не реальной потребностью реализовать именно так, а скорее мотивацией «зырьте как я могу».
Понимание всего этого позволяет взглянуть на язык несколько под другим углом.
defuz
Интересный взгляд, но возникает вопрос, хочешь ли ты сам вступить в ряды этих самых «тысяч» разработчиков.
trong
Ответом на этот вопрос лично для меня является другой вопрос:
Хочешь ли ты быть крут за счет того, что умеешь на питоне извернуться так, что 90% других разработчиков этот код просто не поймут или ты хочешь быть крут потому, что разрабатываешь большой и клевый продукт которым будут пользоваться тысячи? Язык разработки — это большая, но все же часть продукта, который на выходе может быть плох или хорош, и определяется это не только тем, насколько удобно работать со слайсами.
consul, fleet, etcd — вот лишь малый список достойных вещей написанных на Go.
Suvitruf
Странно, что вы Kubernetes не назвали или Docker )
trong
Ну что первым вспомнилось :)
defuz
Мне довольно трудно было бы «чувствовать себя крутым потому что разрабатываешь большой и клевый продукт, которым будут пользоватся тысячи», если делается все для того, чтобы я был не более чем легко заменяемым винтиком внутри системы. Как то мало остантся место для ощущения причасности, не находите? Это тоже самое, что пахать поле руками в толпе себе подобных весь день и чувствовать себя крутым от того, что спасаешь человечество от голодного вымирания. Нет, простите, я лучше научусь использовать комбайн.
Ну и да, у меня как-то и без Go не возникало трудностей «разрабатывать большой и клевый продукт, которым будут пользоватся тысячи» и получалось гораздо читабельне ем выглядит код на Go.
Alex_At_Net
Я хочу :-)
Yuuri
Кажется, когда-то так появилась Java?
neolink
по пунктам:
1) по сути отсылка к отсутствию generics
2) может момент не самый очевидный для данного кода, но если понимать, что такое интерфейс (а это ссылка на что-то) то все логично
3) вы объявили новую переменную, то есть в момент написания этого кода вы допустили ошибку, это не проблема языка он сделал ровно то, что вы от него хотели.
4) есть подозрение, что не от хорошего кода желание такие касты делать.
5) что тут не очевидного, даже синтаксически, вы объявляете новые переменные, почему они должны быть другого типа чем у вас есть в массиве?
после этого вас уже откровенно начинает нести:
> Окей, но как только дело доходит до «читабельности», Роб Пайк решает, что надо ВНЕЗАПНО добавить запятые.
6) это синтаксис, в import и var нет в том месте запятых, а конструкции:
numbers := []int{
3, 5
4
}
явно выглядят куда хуже, кстати запятая в последнем элементе обязательна и проверяется при компиляции, так что пункт вообще не в кассу
7) кодогенерация запускается вручную, явно, отдельной командой.
то есть вместо того чтобы писать https://github.com/golang/net/blob/master/html/atom/gen.go можно сразу описать конструкцию которую можно запустить и сгенерировать новый файл table.go
В общем не сказать, что проблем нет, например если бы добавили опцию при которой можно отключить unused import стало бы чуть лучше, с generics кодогенерация спасает, но для стандартных контейнеров например это не очень классно хранить в каждом месте копию кода, а в реализации на интерфейсах нужно постоянно делать type assertion.
Плохо ещё, что аннотации есть только в структурах, а для типа например их нет.
По сути эти моменты уперлись в ту двухперстную простоту языка с которой он создавался и в 2.0 должен быть неких рефакторинг, но не факт что, что-то сильно поменяется
tgz
Как хорошо, что божественный rust…
Aleksi
… не используется в production. ;)
defuz
почему?
Aleksi
Все известные мне люди, которым нравится Rust, и которых я спрашивал про production, говорили «нет». У меня даже появилось ощущение, что Rust красив только до тех пор, пока на нём не начинаешь писать что-то настоящее.
Но, конечно, это анекдот, в обоих смыслах.
defuz
Я думаю ваши друзья отвечали «нет» в первую очередь потому, что еще некоторое время назад (до выхода версии 1.0) язык очень активно изменялся: обычная практика, когда правильный код не компилировался потому что у вас компилятор недельной давности (такого периода было достаточно, чтобы компилатор можно было считать устаревшим). Также, сразу после выхода 1.0, много важных вещей в стандартной библиотеке еще не было стабилизировано. Это добавляло ощущения нестабильности.
Но, к счастью, Rust уже дорос до 1.4, большинство API стабилизировано, новые библиотеки растут как на дрожжах и ничего не ломают. Конечно, это не значит, что всем стоит бросится писать на Rust, но если предположить, что код на Rust уже добрался до продакшена, то там у него точно будет все хорошо.
Gorthauer87
Библиотеки часто пока не так хороши, как сам язык, вроде батареек и много, но они еще сыроваты. Да и пока нет ничего вменяемого для UI. А язык, вроде бы, должен подходить для программирования UI.
Ну и в bare metal разработке тоже пока все сыро, хотя и выглядит крайне вкусно.
А еще пока нет нормальных средств интернационализации.
Alesh
Думаю просто потому что растаманы маленько торопятся с номерами версий. И еще никто не зарелизил на нем что-то типа докера. Но полагаю ждать уже осталось недолго.
И да, он действительно красив)
Stronix
copiedNumbers := numbers[:]
JIghtuse
В Python работает так же, к слову.
neolink
не совсем (вернее это совсем не копирование): http://play.golang.org/p/b2hrKq5rem
об этом даже прямо в блоге написано: http://blog.golang.org/go-slices-usage-and-internals
> Slicing does not copy the slice's data. It creates a new slice value that points to the original array
JIghtuse
Ого, а это неожиданно. То есть, единственный способ — это указанный в статье?
Stronix
Вот ещё вариант
neolink
ну да, только надо глянуть есть там аллокация на пустой массив или этот момент оптимизирован
Stronix
Хм, да, вы правы.
Stronix
0xd34df00d
Эм, а какие выражения допустимы в контексте индексов слайса?
Stronix
Про range вообще не понял, там же ясно стоит :=
Stronix
golang.org/doc/faq#nil_error
mayorovp
Да, это объясняет почему так получается — но не объясняет зачем так сделано.
Aleksi
Это частный случай Zero value. Во всех остальных случаях оно очень удобно и эффективно.
dmbreaker
Складывается ощущение, что автор не очень хорошо знает Go и имеет не так много опыта в разработке вообще. Но это ладно.
1) То что описано в реальном коде приходилось использовать от силы один раз. А частые операции со слайсами в Go куда понятнее и лаконичнее. В общем, если автору частно приходиться писать такой код, то может и не в языке дело.
«numbers[:-1]» — ну это просто Python головного мозга. Зачем требовать фич одного языка у другого, если другой намеренно хочет избежать опасных конструкций.
2) nil interface. Пункт с которым я соглашусь. Считаю это багом, который почему-то не хотят исправлять.
3) Ну сокрытие, ничего забавного — все очевидно. Если ты хороший разработчик — это не проблема. Причем одно забавное сокрытие в Go таки есть, но автор про него не знает, оно связано с именованными возвращаемыми значениями. Впрочем статические анализаторы на это обычно ругаются.
4) К сожалению передать []struct как []interface передать нельзя, было бы чуть удобнее, но откуда истерика по этому поводу?
5) foreach. Если вдумчиво читать стандарт языка, то все становится очевидным. Приводить в пример все остальные языки не имеет смысла, т.к. во многих из них нет понятия указателя. В данном случае это критично. Автор приводит в пример C++. Давайте смотреть ретроспективно — сколько прошло времени, чтобы в C++ вообще появился foreach?
6) unused imports — это наоборот то, что в Go прекрасно. Сначала раздражает строгость, но спустя некоторое время привыкаешь и испытываешь удовольствие от чистоты кода, в котором нет лишних неиспользуемых переменных и импортов. Это и полезно — по первым строчкам становится ясно что использует файл, а что нет. И авторы заморачивались не только на простоту, как думает автор, но и на скорость компиляции, лишние импорты при этом совершенно ни к чему. И многие конструкции в языке строги, подозреваю, отчасти и по этой причине.
7) Про кодогенерацию ничего не скажу, т.к. не приходилось использовать.
Итого, смотрим на эти пункты. Кто, откровенно может сказать, что эти пункты вообще что-то говорят про плохой дизайн языка?
Да, есть недочеты, но в C++ их куда больше, причем это не такие безобидные вещи — там с легкостью можно отстрелить себе обе ноги носовым платком.
Везде есть свои особенности и недостатки — идеала нет. Но, с моей точки зрения и опыта, это один из языков с лучшим дизайном, при этом минималистичным. Это не значит, что Go можно изучить за 2 недели. Нет, за 2 недели на нем можно научиться писать и если на этом остановиться, то будут появляться такие статьи. Чтобы изучить Go нужно хотя бы месяца 4 активной разработки и параллельного изучения особенностей и конструкций языка. Сладкий фрукт, да не каждый до него достает.
Stronix
Возвращаясь в полусонном бреду, без всяких проверок… play.golang.org/p/22eNJO0Js3
nwalker
Код, в котором есть interface{} не имеет права называться красивым.
mirrr
Я уже таких выше наплодил) Им нужно с проверками типов во время компиляции)
blackstrip
Наконец-то пост ненависти про Go. А то все предыдущие статьи — один пиар и хомяк этот кривой на логотипах. Слишком форсят свой суперязычок. Хочется к его названию подписать «вно».
semenyakinVS
Интересы: «Assembler, Delphi, Интерфейсы». Конечно, у Delphi более ясное будущее чем у Go.
Хабраслив или тонкая игра в провокацию?
blackstrip
не, просто люблю комментировать спустя два дня, никто не заминусует, можно спокойно высказать мысли. Дельфи — паскаль, стоящий отдельным синтаксисом с зари времен, с ним рядом стоят бейсик, си-подобные языки. Это всё будет жить вечно, как и процессорные коды. А новомодное дмецо, которое по хорошему на одну ладонь положить, другой прихлопнуть и растереть — лезет сегодня из всех дыр. Каждый хочет урвать кусок пирога, выдвинуть свой суперпуперязык или новый синтаксис (с миллионом скобочек и двоеточий, «облегчающих» написание кода) на первое место чтоб школота неистово кодила на всем этом скопище манкиязычков. Я из движения хейтерства и сопротивления этому процессу.
semenyakinVS
Д-р, простите, не узнал вас в гриме.
«Теперь весь мир стоит на краю, глядя вниз на чертово пекло. Все эти либералы, интеллектуалы, сладкоголосые болтуны. И отчего-то вдруг никто не знает, что сказать. Подо мной этот ужасный город, он вопит как скотобойня полная умственно отсталых детей, а ночь воняет блудом и нечистой совестью»
А если серьёзно — удачи. Всегда уважал фанатиков и верил что пусть даже окольными путями лишь фанатики могут принести какие-либо плоды. Злые ли, добрые будут эти плоды — другой вопрос. Но лишь одержимые движут мир.
Daniro_San
Ох уж эти масоны.
Хотят поработить мир с помощью Go
KIVagant
> Причина №1. Манипуляции со слайсами просто отвратительны
Согласен
> Причина №2. Нулевые интерфейсы не всегда нулевые :)
Согласен, что это нелогично
> Причина №3. Забавное сокрытие переменных
Вообще не понял, почему автор ожидал 42 в последней строке. По-моему, поведение Go в этом случае верное.
> Причина №4. Ты не можешь передать []struct как []interface
Для начала хочется сказать, что тип rune для int32 вообще вызывает странное ощущение. Почему было не оставить просто int32, не понимаю?
А с самой причиной согласен в том контексте, что в Go (ну прям как в до боли знакомом php) нужно помнить про очередные подводные камни. Со временем к ним так привыкаешь, что даже и не воспринимаешь как проблему. Типа «да это же все знают!».
И да, если вспомнить про реализацию чего-то, подобного DI, с ожидаемыми параметрами с типами определенных интерфейсов, то причина выглядит серьёзной.
> Причина №5. Неочевидные циклы «по значению»
Ну смотря для кого неочевидные. Как по мне, отсутствие явного указателя передачи по ссылке уже говорит, что будет возвращено только значение.
> Причина №6. Сомнительная строгость компилятора
Согласен полностью, во время отладки бесит всё время комментировать и раскомментировать подключаемые библиотеки для отладки.
> Причина №7. Кодогенерация в Go это просто костыль
Согласен. Вообще против концепции генерации кода. Если язык не в состоянии предоставить удобные инструменты из коробки или с помощью фреймворков, то что-то в нём не так.
P.S.:
От себя добавлю ещё вот этот кейс, с которым я столкнулся при попытке написать первую простенькую программку на Golang, и который был сразу же заминусован на SO.
mayorovp
Когда уже перестанут называть рейтинг -1 словом «заминусован»?..
KIVagant
Да, я не успел это отредактировать, вы наблюдательны. (Кстати, он был -2, хех)
divan0
rune — это специальный тип данных для работы с Unicode. Формально это алиас на int32, но выделен в отдельный тип, так как представляет конкретную сущность — «букву», или же codepoint.
habrahabr.ru/post/269887
KIVagant
Я прочитал статью. Честно, я все-равно воспринимаю это как костыль в коде. Не понимаю, почему нельзя было решить это через концепцию управления зависимостями, например? Почему не билдить все дополнительные библиотеки используя список зависимостей, собранный в управляющих файлах? Пусть это будет та же команда go generate. Какая ей технически разница, откуда читать информацию — из файла или из отдельного конфига? Ладно, если кому-то хочется держать всю информацию о зависимостях в самом файле, то с чего вдруг это указывается в комментарии да ещё и со странными требованиями в виде «не должно быть пробела после //»?
Нет, вы меня не убедили.
beresovskiy
namespace, поправьте первую строку, указывающую на перевод, пожалуйста, чтоб читатель понимал, что ориганал тоже был написан вами.
namespace
я немного подумал и решил, что не хочу делиться с читателем этим скромным фактом.
beresovskiy
Вам решать, но divan0 тогда прав, утверждая, что ваш пост «был выдан за перевод» (http://habrahabr.ru/post/269817/).
namespace
Так это и есть перевод, это перевод моей блогозаписи. Фраза «был выдан за перевод» звучит так, как будто я кого-то обманываю!
beresovskiy
Я понял, что вы автор оригинала и перевода лишь после того, как я узнал, что tucnak@medium == namespace@habr.
namespace
Ничего себе, вы случайно не чувствуете себя обманутым?!