Послевкусие от «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.
Есть такой казус. Да.
В 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-го. Но ничего особо не поменялось с тех пор. Видимо всех всё устраивает.
Комментарий для 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.ru:
Я тоже считаю, что преинициализация это благо. Это много проще для компилятора, снижает расходы.
Я же не про это совсем писал, вы прочитали текст целиком? Я про то каким образом можно было бы поправить ситуацию с типом «map», рассматриваю различные подходы.
delete a[i] разве не подходит? По-моему лучше чем введение _.
Комментарий для Павел Власов:
Я описывал ситуацию с массивами — выход за границы приводит к panic и завершению программы. map ведёт себя гуманнее, он просто выдаёт, специальным образом, что элемента нет.
Если под delete a[i] предлагается синтаксическая обёртка вокруг текущей ситуации удаления, то неважно, можно просто написать функцию delete, она будет делать то же. Но в этом случае присваивание «_» логичнее для этого языка.
Если же предлагается ввести конструкцию, которая будет уничтожать значение и поведение map изменится (будет panic), то я первый буду против. Во-первых, сломается существующий код, во-вторых, в языке panic довольно неудобно перехватывать (я ещё напишу об этом).
Комментарий для Евгения Степанищева:
Если вспомнить, как позиционируется язык, всё встаёт на свои места. Когда его анонсировали, Google говорили, что Go — язык для высоконагруженных и масштабируемых систем, а это предполагает активное распараллеливание, ну а распараллеливание, сами понимаете — это царство immutable-объектов.
Комментарий для http://twitter.com/Flash_it:
Почему вы immutable объекты вспомнили? Рассматриваемый тип map — mutable.