четверг, 25 июля 2013 г.

[prog.thoughts] И еще на тему Go (после прочтения всего Effective Go)

Странные ощущения меня одолевают после штудирования Effective Go (а до этого и A Tour of Go). Попробую перечислить основные моменты.


Во-первых, не могу сказать, что из прочтенных документов я понял, как на Go программировать. Очень вероятно, что это моя менеджерская тупость. Но как-то не сложилось у меня в голове картинка. Когда изучал Ruby по отличной книге Programming Ruby, то представлял себе из чего состоит Ruby-новая программа. Когда изучал Eiffel по "Объектно-ориентированному проектированию программных систем" -- то же самое. А вот здесь что-то не понимаю. Должна ли программа на Go быть похожа на C-шную программу с функцией main, набором функций и структур данных? Или же это должно быть больше в стиле Java: набор классов для решения прикладной задачи?

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

Вообще, очень сильное ощущение, что Go предназначен для разработки мелких программ на выброс утилит в духе unix way: одна операция на утилиту, для более сложных задач собирается конвейер из подобных мелких утилит.

И еще одно ощущение, от которого не удается отделаться. Каналы -- это, очевидно, киллер-фича языка. Но что привлекательного остается в языке, если не пользоваться каналами? Например, для написания большого фрагмента кода, решающего чисто вычислительные задачи? Будет ли Go удобен для обработки сложных структур данных? У меня нет даже предположений по этому поводу.

В общем, после более плотного знакомства с Go вопросов и недопонимания оказалось больше, чем я ожидал. А желания использовать Go, напротив, намного меньше. Возможно, попрограммируй я на Go пару-тройку месяцев, все бы встало на свои места. Но вряд ли я это буду делать.


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

Я когда-то писал про презентацию Роба Пайка о языке Go. В которой делается попытка обосновать причины появления Go. Но я не был полностью удовлетворен этими причинами тогда. Сейчас же непонимание усилилось еще больше. Особенно после беглого просмотра презентаций, ссылки на которые были даны в этом посте: Advanced Go Concurrency Patterns

Если Go решал проблемы многозадачности (не придумал лучшего термина в этом контексте для concurrency), то почему нельзя было обойтись библиотекой или набором библиотек для C++? Аналоги Go-шных каналов на шаблонах C++ могут быть написаны, да так, чтобы и пользоваться ими было удобно, и в эффективности они не теряли. Да, для C++98/03 это было бы многословно, гораздо многословнее, чем на Go. Но те самые паттерны, о которых говорят разработчики Go, могли бы быть использованы на уже существующем языке с большим количеством готового инструментария и большущей армией опытных программистов во всем мире.

Однако, очевидно, что в языке с ручным управлением памятью (таком как C++ или C) написание многозадачного кода не есть простая тема. Я на себе это прочувствовал, когда делал и использовал SObjectizer. Так что сочетание каналов со встроенным в язык сборщиком мусора -- это разумный ход. (Хотя здесь можно было бы подискутировать о том, насколько сложно потом будет бороться с эффектами сборки мусора в нагруженных приложениях. И о том, какого качества сборщик мусора будет на первых порах в Go. Но лучше оставить эти вопросы за рамками обсуждения.)

Но тогда возникает вопрос, почему за основу не был взят какой-нибудь управляемый язык со сборкой мусора? Та же Java, например. Тем более, что в Google, насколько мне известно, Java давно и активно используется.

Аргументы против Java, конечно же, лежат на поверхности. Это и очень большая многословность, и не такая высокая производительность (как у хардкорного C/C++ кода), и плохая приспособленность для околосистемных/низкоуровневых задач (например, отсутствие беззнаковых целых чисел, отсутствие контроля за расположением данных в памяти и пр.). Но тут еще как посмотреть: вопросы производительности, например, для сервер-сайда не так критичны, как для десктопа или чистых числодробилок.

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

С другой стороны, создан совсем новый язык, в котором несколько фич выглядят очень здорово и позволяют записывать некоторые приемы многозадачного программирования весьма компактно. Но при этом новый язык:

  • поддерживает какую-то мутную схему обработки ошибок посредством panic-ов и recover-ов (подробнее см.здесь);
  • не имеет серьезной кодовой базы и большого количества инструментальных средств. Плюс к этому полное отсутствие опытных разработчиков на рынке труда;
  • и, самое главное для меня, не имеет никаких средств для обобщенного программирования.

И вот этот последний пункт, на счет обобщенного программирования, для меня наиболее важен и непонятен. Сейчас все более-менее заметные в мейнстриме языки поддерживают обобщенное программирование: в C++ есть шаблоны, в Java/C# -- генерики, в Scala, например, без генериков вообще никуда. В Haskell-е, насколько я понимаю, обобщенное программирование -- это краеугольный камень. Про динамически типизированные языки (Python, Ruby, JavaScript) даже не приходится говорить. А вот в Go с этим просто швах. Никаких шаблонов. Хочешь чего-нибудь обобщенного -- прячь все это за интерфейсами. А вот получится ли у тебя на интерфейсах сделать какой-нибудь сложный контейнер, который мог бы хранить как string-и, так и пользовательские структуры или int-ы -- это большой вопрос. Подозреваю, что не получится.

Ну и еще одно. При всех своих проблемах и недостатках те же C++ и Java доказали, что они позволяют создавать большие и сложные программные комплексы из больших и сложных библиотек функций/шаблонов/классов. Возможно, отдельные "недостатки" этих языков как раз таки являются ценой, которую приходится платить за эту самую возможность строить очень большое и очень сложное из больших и сложных частей. Предназначен ли Go для того, чтобы создавать большие приложения из компонентов, сравнимых по объему с Qt/ACE/ICU/Boost (для C++) или Eclipse Platform (для Java)? Я почему-то думаю, что нет. И это так же заставляет задуматься о том, зачем он вообще нужен.


В-третьих, похоже, вообще шансов для попадания в мейнстрим у новых языков нет. Если только новый язык не является просто синтаксическим сахаром над наработками на более старом мейнстримовом языке (например, Scala или Ceylon над Java-библиотеками для JVM).

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

Но вот как менеджер я понимаю, что работать нужно здесь и сейчас, быстро и качественно. Это означает, например, что если тебе приходится иметь дело с СУБД MS SQL Server, ты не можешь позволить себе искать более-менее работающий ODBC драйвер для нового языка программирования на каком-нибудь github-е или SourceForge. И не можешь позволить себе писать какую-нибудь библиотеку с нуля или же помогать кому-нибудь в ее создании. Потому что вокруг уже есть аналоги для C/C++, Java, C# или даже Haskell-я. Более взрослые, функциональные, отработанные, со сложившимися коммьюнити. И уж если тратить свое время на помощь кому-то, то лучше присоединяться к серьезным, устоявшимся проектам. Риски в этом случае меньше.

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

  • чистый нэйтив в лице C и C++;
  • Java и языки вокруг JVM (Scala, Ceylon, Kotlin и иже с ними);
  • C# и .NET (включая Visual Basic и экзотику вроде F#);
  • JavaScript (как client-side в браузере, так и средство разработки UI для десктопа);
  • динамически-типизированные языки, каждый из которых является собственной программной платформой: Perl, Python, Ruby, PHP, Erlang.

Ну и плюс к этому нишевые экосистемы, размер и значимость которых я не могу определить: Objective-C (платформы Apple), нативная функциональщина (в первую очередь Haskell, OCaml, различные варианты Lisp-ов и ML-ей), Adobe ActionScript (Flash-овый client-side в браузере).

Такие экосистемы сложились не сегодня, и даже не вчера. Но с каждым днем их размер, а так же взаимные различия, только увеличиваются. Поэтому новым языкам нужно уметь встраиваться в какую-то из них максимально прозрачным образом: обязательно уметь использовать уже существующий код и желательно уметь предоставлять старому коду возможность взаимодействия с новым кодом. Как раз то, что демонстрируют Scala и Ceylon в мире JVM.

Объясняется моя точка зрения очень просто. Я не верю в то, что кто-то сейчас может вложиться и написать для нового языка что-то сравнимое с Qt или JDK. ИМХО, это не реально. Да и вряд ли кому-то нужно. Поэтому у новых языков, если они претендуют на звание универсальных языков общего назначения, нет другого выхода, кроме как позволять очень простым способом использовать написанное ранее для других языков.

И вот здесь у чистых нейтив языков ситуация совсем аховая. Если интегрироваться с C еще возможно, то вот как интегрироваться с C++ным кодом, да еще с современным шаблонным C++ным кодом?

В этом плане как раз у фунциональшины в лице Haskell-я ситуация намного лучше, чем у Go, D или Rust. Там совсем другой подход к программированию. Поэтому-то и библиотеки нужны совсем другие, ценность плюсовых библиотек для Haskell-я невелика.

Вот и получается у меня, что в чистом нейтиве нормальные шансы на развитие есть только у C/C++ и у Haskell-я. Всему остальному придется создавать/отвоевывать себе узкую нишу, баррикадироваться там намертво и никого не пущать :)

Комментариев нет: