Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Послевкусие от «Go»

Должен сказать, что у «Go», как, наверное, у любого нового языка программирования, в изобилии свои, модные, ни на что не похожие тараканы. Небольшой пример приведу перед сном.

У «Go» есть хеши, называются «map», описываются несколько необычно: нужно описать не только тип значений, но и тип ключей. «Go» язык строгий, для него это важно:

package main
import `fmt`

func main() {
    arr := map [string] byte { `a`: 1, `b`: 2}
    fmt.Println(arr)
}

Эта простенькая программка выведет на экран что ожидается: «map[a:1 b:2]», то есть строковое представление нашей переменной типа «map». Давайте представим, что из переменной «arr» нужно удалить какое-то значение по ключу. Что для этого нужно сделать, как вы думаете?

Можете прочитать руководство и попробовать найти нам функции «del», «delete», «remove», «destroy», что угодно, ничего такого вы там не найдёте. Попытка присвоить «nil» ни к чему не приведёт — язык не позволит это сделать. Что делать?

Можно, конечно, пересоздать переменную с новым значениями, но есть способ… э… лучше. Почему я в этом не уверен будет понятно дальше.

Каждый элемент переменной типа «map» содержит специальную магию. Если запросить у элемента не одно, а два значения, то вернутся два — значение элемента и флаг, существует ли он:

val, exists := arr["a"]
fmt.Println(val, exists) // выведет «1 true»

Чтобы удалить значение из хеша надо присвоить второму значению «false»:

arr["a"] = arr["a"], false // a["a"][1] = false не сработает, у него другой смысл
fmt.Println(arr) // выдаёт map[b:2]

val, exists = arr["a"]
fmt.Println(val, exists) // выведет «0 false», значение сброшено и «не существует»

Если запросить у хеша значение с ключом, которого никогда в нём не было, эффект будет тот же — нулевое или эквивалентное ему значение и «false», тут всё логично.

Выбор такого решения для удаления значения мне кажется странным.

На всякий случай, приведу место из официального руководства, где это всё описано, чтобы не возникло ощущение, что я просто его прочитал невнимательно:

Similarly, if an assignment to a map has the special form a[x] = v, ok and boolean ok has the value false, the entry for key x is deleted from the map; if ok is true, the construct acts like a regular assignment to an element of the map.

8 комментариев
rodem 2011

Есть такой казус. Да.
В GoNuts народ долго негодовал «awkward key deletion syntax», «delete depending on the value of a boolean variable could make code completely unreadable», «The syntax of deletion is just stupid. Why would a user need to provide a value if he wants to delete the entry and this value will be ignored anyway?»
Причём давно, ещё в декабре 2009-го. Но ничего особо не поменялось с тех пор. Видимо всех всё устраивает.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для rodem:

Возможно, ничего не поменялось, потому что не предложили ничего дельного. Можно зайти с другой стороны — как проверить, что такого значения с ключом там нет? Язык не поддерживает неинициализированные переменные, если я напишу «var v int», то значение переменной «v» будет ноль.

То есть «undefined» у нас нет. Как же узнать, что мы запрашиваем ключ, значения для которого не существует в нашей переменной? Есть тип с похожим поведением — массив. Как же происходит это у массивов? На мой взгляд, там это сделано ещё хуже, программа останавливается, если выйти за его приделы:

panic: runtime error: index out of range

runtime.panic+0xac /Users/bolk/go/src/pkg/runtime/proc.c:1083
runtime.panic(0x55530, 0xf840001380)
runtime.panicstring+0xa3 /Users/bolk/go/src/pkg/runtime/runtime.c:116
runtime.panicstring(0xfe6d6, 0x0)
runtime.panicindex+0x25 /Users/bolk/go/src/pkg/runtime/runtime.c:73
runtime.panicindex()
main.main+0x40d /Users/bolk/Проекты/HTTPHere/strange.go:21
main.main()
runtime.mainstart+0xf /Users/bolk/go/src/pkg/runtime/amd64/asm.s:77
runtime.mainstart()
runtime.goexit /Users/bolk/go/src/pkg/runtime/proc.c:150
runtime.goexit()
-​-​-​-​-​ goroutine created by -​-​-​-​-​
_rt0_amd64+0x8e /Users/bolk/go/src/pkg/runtime/amd64/asm.s:64

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

В баг-трекере языка «Go» ( http://code.google.com/p/go/issues/detail?id=561%26q=label%3ALanguageChange%26colspec=ID%20Status%20Stars%20Priority%20Owner%20Reporter%20Summary ) предложили логичное решение для удаления значения из map: m[k] = _.

Осталось сделать такое же сравнение и задачу можно считать решённой, но это серьёзное изменение языка — по сути у map вводится ещё одно состояние переменной «неинициализированна». Что это может за собой повлечь пока неясно, но интуитивно я чувствую, что такие серьёзные изменения могут привести к тому, что серьёзная часть программ опять перестанет компилироваться.

Если ещё вариант поломать логику языка и оставить присвоение «_» и сравнение с этим значением как синтаксический сахар. Но, во-первых, рушится единство языка, во-вторых, внутренний механизм реализации запрятывается уровнем глубже, есть вероятность, что программист не будет вспоминать о нём годами и лишь при попытке сделать «v1, v2 = m[k]» столкнётся с тем, что что-то не работает, а почему неизвестно.

Orcinus Orca (orcinus.ru) 2011

Если вспомнить первые паскали и прочие ЯВУ, то там просто в оперативной памяти выделялось место со всем его мусором. Никакой преинициализации, никакого сбора мусора, вообще ничего. По этому, я считаю, что предварительное присвоение нуля — это благо. А игра с возможностью использования неинициализированных переменных не столь критична. Если хочется контролировать инициализирована или не нет переменная, то можно предусмотреть флаговый аппарат и там уже выставлять инициирована переменная или нет.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для orcinus.ru:

Я тоже считаю, что преинициализация это благо. Это много проще для компилятора, снижает расходы.

Я же не про это совсем писал, вы прочитали текст целиком? Я про то каким образом можно было бы поправить ситуацию с типом «map», рассматриваю различные подходы.

Павел Власов 2011

delete a[i] разве не подходит? По-моему лучше чем введение _.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для Павел Власов:

delete a[i] разве не подходит? По-моему лучше чем введение _.

Я описывал ситуацию с массивами — выход за границы приводит к panic и завершению программы. map ведёт себя гуманнее, он просто выдаёт, специальным образом, что элемента нет.

Если под delete a[i] предлагается синтаксическая обёртка вокруг текущей ситуации удаления, то неважно, можно просто написать функцию delete, она будет делать то же. Но в этом случае присваивание «_» логичнее для этого языка.

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

twitter.com/Flash_it 2011

Комментарий для Евгения Степанищева:

Если вспомнить, как позиционируется язык, всё встаёт на свои места. Когда его анонсировали, Google говорили, что Go — язык для высоконагруженных и масштабируемых систем, а это предполагает активное распараллеливание, ну а распараллеливание, сами понимаете — это царство immutable-объектов.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для http://twitter.com/Flash_it:

Почему вы immutable объекты вспомнили? Рассматриваемый тип map — mutable.