Блог им. LRNЗлые языки программирования "новой волны" [1/2]

Злые языки программирования «новой волны» [2/2]
«Языки программирования „новой волны“ — так называется цикл статей Семена Есилевского в электронном журнале-приложении OpenSource. Ещё Выпуск №91 резанул глаз статьёй про язык программирования Go. Но лишь с выходом 92-го номера журнала стало ясно: дело табак. Начинается статья 91-го выпуска не так уж плохо: объясняется происхождение языка, даётся краткий список того, что в нём есть, и чего нету. А вот потом начинается странное:
Go позиционируется как язык, ориентированный в первую очередь на написание серверных частей web-приложений (именно для этих целей он используется в Google). Он не является универсальным системным языком, таким как С++ или D. Основная ниша Go – программы, в которых нужно порождать множество взаимодействующих друг с другом потоков (что, как правило, и нужно для web-сервисов). В какой-то степени Go – это императивный аналог функционального языка Erlang.
0) Go был задуман как systems language, но оказалось, что на нём можно писать ещё много всякого и много разного, поэтому теперь его позиционируют как general purpose language. 1) В Go, в отличие от Erlang, нету горячего патчинга (если говорить о программировании надёжного серверного ПО). 2) Erlang использует акторную модель, а Go наследует от CSP.
Синтаксис Go не отличается внутренней элегантностью и продуманностью, так что, не изучив язык основательно, понять написанные на нем программы очень сложно.
Тут следовало бы добавить „ИМХО“, и упомянуть, что большинство языков всё-таки надо изучать, чтобы понять написанное на них (скорее всего автор подразумевает, что любой язык, не похожий на C — непонятная ересь).
В синтаксисе имеются и странные детали в «эзотерическом» стиле Perl и TCL (достаточно упомянуть управление областью видимости символов с помощью их капитализации, непонятные ограничения на форматирование кода и возврат функциями нескольких значений).
Интересно, что Python здесь не упомянут, хотя в нём есть все три „эзотерических“ фичи: управление публичностью/приватностью членов классов с помощью добавления префикса „__“, ограничения на форматирование кода и возврат функциями нескольких значений. Но к Питону мы ещё вернёмся.
… входящий в состав коллекции компиляторов gcc.
Тут к месту было бы упомянуть, что многие дистрибутивы GNU/Linux распространяются на CD-дисках. Да, к мелочам я тоже придираюсь.
Gc на данный момент не входит в состав ни одного из дистрибутивов Linux
»Данный момент" — это, видимо, где-то 20-е сентября 2011. Из этого становится очевидно, что Debian не являетсядистрибутивом Linux, ибо в unstable пакет golang появился в феврале 2011, а вскоре после этого — и в testing.
Современные системы сборки, такие как CMake и Scons, пока не поддерживают go и это заметно снижает переносимость программ, написанных на этом языке (наладить компиляцию с помощью make-файлов в Windows не так просто).
1) goscons, sconsgo и scons-go-tools (собственно, ссылки взяты отсюда) — не в счёт, ведь Scons не является расширяемой системой сборки, и писать для неё дополнительные билдэры нельзя. 2) mingw-get, тоже не в счёт — ведь скачать инсталлятор и сделать где-то 3 клика — это «не так просто», как кажется, ибо использование менеджера пакетов — задача для программиста нетривиальная.
Не смотря на такое многообразие утилит, в gc пока нет отладчика. Его создание входит в планы разработчиков, но когда он будет готов, пока неизвестно. С gccgo можно использовать обычный gdb, но и он пока не поддерживает никаких специфических для Go конструкций и типов данных.
Видимо поддержка gc в gdbне считается. Дальше идёт обширное описание IDE. Наверное кому-то это нужно… Хорошо хоть, что возможность использования текстовых редакторов для написания кода была упомянута, а то так бы и сидели, дожидаясь созревания IDE… Но перейдём же к следующему выпуску журнала!
Описывая синтаксис языка Go, трудно удержаться от постоянного повторения вопроса: «Неужели нельзя было сделать по-человечески?». По количеству не интуитивных и нечитаемых конструкций Go, конечно, не может тягаться с Perl и C++, но их все равно неоправданно много. В Go нет ни идеальной отточенности синтаксиса языка D, ни подкупающей простоты Python. И дело не только в непривычности, но и в невозможности понять, чем вызвана такая реализация.
«По-человечески» — это, наверное, «как в C». А вот тут Питон упомянут. Ясно, что Go не научился ещё давать взятки, и поэтому не подкупает своей простотой. А Питон — подкупает. Ну а чем объясняется невозможность обретения понимания, станет ясно чуть ниже.
Несколько выражений в одной строке обязательно должны разделяться точкой с запятой, но точку с запятой в конце строки Go вставляет автоматически, проявляя лишь минимальную «интеллектуальность». Так, после «main() {» точка с запятой вставляться не будет, но если написать:
func main() {
– это вызовет синтаксическую ошибку, поскольку компилятор, не задумываясь, добавит знак «;» после main(), не утруждаясь разбором семантики кода. А ведь в этом случае достаточно было бы всего лишь убедиться, что между «()» и «{» нет ничего кроме пробелов и, возможно, комментариев… Подобные странности накладывают совершенно излишние ограничения на форматирование кода.
Объяснение дано в FAQ. Но разве настоящие программисты читают FAQ, знакомясь с новым языком? Нет!
Синтаксис опять же выглядит, как «Си наизнанку», и вызывает недоумение.
Самое страшное заключается в том, что на самом деле это в С типы определяются задом наперёд. А в Go всё «прямо». Думаю, приведённые в посте по ссылке примеры убедят любого опытного С-шника в том, что есть более удобные способы определения типов, чем в C.
И тут начинаются нюансы языка Go. Так, кроме make в Go есть и оператор new, но делает он совсем другое:
var p *[]int = new([]int) // Указатель на неинициализированный срез неизвестного // размера, который никому в таком виде не нужен
Make используется только для срезов, ассоциативных массивов и каналов (о них ниже), а new – для всех других типов данных, включая пользовательские. Возникает резонный вопрос: зачем городить отдельный оператор для пары встроенных типов? Указатели в Go тоже довольно странные: по ним можно выделять память, но для них нет арифметики, как в С/С++.
По поводу new() и make() FAQ весьма краток, однако таки даёт ссылку на Effective Go, где различие между new() и make() описано в деталях. Вкратце скажу лишь вот что: Помни, юный павиан, что в Go нету классов -> нету конструкторов. make() — вместо конструктора. new() — вместо malloc0(). Отсутствие же арифметики на указателях объяснено в FAQдостаточно полно.
Ассоциативные массивы встроены в язык и выглядят так:
var days = map[int]string { 1: "понедельник", 2: "вторник", 7: "воскресенье", }
В скобках задается тип ключа, а за ним – тип значения. Запятая после последнего элемента – указание компилятору, что там не нужно вставлять точку с запятой (странно, что нельзя было сделать немного более продвинутый семантический анали затор).
Однако же Питон такое позволяет, и даже поощряет. А, ну да, я забыл, что Питон умеет давать взятки, и подкупает своей простотой. А про точки с запятой уже говорилось выше.
Наличие элемента в массиве проверяется так:
if value, ok := days["тяпница"]; ok { fmt.Println(value) } else { fmt.Println(“нет такого дня!”) }
Запись «value, ok := что-то-возвращающее-два-значения» – это идиома «запятая окей», используемая в Go повсеместно. При этом «ok» – логическая переменная, означающая успешность вызова в правой части присваивания. В большинстве других языков это сделано с помощью days.exists(«тяпница»).
Кстати, уже второй кусок кода со сбитыми отступами (да, в PDF так и было, это не я испортил). Мелочь — но впечатление от прочтения резко портится. Но вернёмся к семантике. «Скорость, надёжность или простота — выбери что-то одно (в лучшем случае — два)». Это часто приводимое разработчиками Go высказывание, относящееся к другим языкам. Go же пытается более-менее успешно соединить все три. Вопрос: как ты собираешься получить «скорость», если ты сначала делаешь days.exists(«тяпница»), а потом — drinkday_schedule = days[«тяпница»]? Ибо в общем случае тебе надо не только существование ключа проверить, но и содержимое по нему вытащить. Я так полагаю, что гораздо эффективнее сделать lookup один раз, и заодно вытянуть значение по найденному ключу (если найден). Питон, кстати, тоже умеет что-то подобное, в дополнение к «x in y» и «y.haskey()» (ещё в Питоне можно тупо делать lookup без предварительной проверки, а потом обрабатывать эксэпшн — но нафига, если можно проще и быстрее? Об эксэпшнах, кстати, будет ещё, чуть ниже).
Запись нескольких операторов в условии ветвления наследует худшие черты языка Си и, на мой взгляд, является типичным примером «индийского кода». Тем не менее, она пропагандируется в Go как правильный стиль.
А как насчёт записи трёх (!) стэйтментов (или всё-таки выражений?) (тут ещё можно вступить в долгий спор о том, что является «операцией» и «оператором», но такая тема интересна лишь академическим программистам) внутри стэйтмента «for(;;)»? Это тоже худшая черта языка C? Так вот, в Go стэйтмент «if» тоже может иметь инициализатор. То есть, это вовсе не сакраментальное
if ((foo = bar(baz)) == NULL) { ... }

Так можно удалить элемент из ассоциативного массива:
days["понедельник"] = 0, false // Понедельник – день тяжелый //он не нужен
После этого окончательно осознаешь странность логики Go. В самом деле, чем плох вариант days.remove(«понедельник»)? Его ведь хотя бы можно прочитать на человеческом языке...
Обсуждалось тут (и ещё в двух других местах, но тут отписались Роб и Расс). Вкратце: если ты знаешь про «val, ok = map[key]», то «map[key] = val, ok» — это то же самое, только наоборот. Ещё есть соответствующая ишша №561. А вообще, попробуй определить метод remove() на map'е. Вроде язык позволяет… надо будет попробовать самому.
Функции в Go записываются в стиле Pascal с возвращаемым значением после заголовка, что для С-подобного языка является нонсенсом:
func add(a int, b int) int { return a+b }
О прямом и «спиральном» порядке дефинишнов писалось выше.
Но большее удивление вызывает, что функция может возвращать несколько значений:
func add_sub(a int, b int) (int,int) { return a+b, a-b }
По сути такая возможность – всего лишь синтаксически облегчённая версия кортежей, которые есть в Python или D.
Надеюсь, удивление приятное? Типа «ни в одном статически-типизированном комилируемом в нативный код языке, кроме D, нет таплов, а в Go — есть! Ура! Можно возвращать несколько значений из функции без использования указателей!». Или всё-таки неприятное? Благодаря общему тону статьи создаётся именно такое впечатление. Продолжение следует. Все комменты — туда (хотя лучше не надо комментов — холивар же начнётся...).
  • +7
  • LRN
  • 29 сентября 2011, 01:03

Комментарии (0)

Автор топика запретил добавлять комментарии