GrabDuck

Эволюция Go

:


Подошедший к концу GopherCon 2015, длившийся с 7 по 10 июля в Денвере, отметился немалым количеством интересных выступлений. Видео докладов еще недоступно, однако, конспекты некоторых из них доступны на английском языке по этому адресу; в официальном блоге также можно прочитать программную речь Расса Кокса. Вниманию читателей предлагается конспект доклада об истории создания языка, который открыл второй день конференции.

Роберт Грисмер ( @robertgriesemer) — разработчик в Google, один из авторов языка Go. В прошлом Роберт работал над генерацией кода для высокопроизводительного JavaScript, занимался языком программирования Sawzall и разработкой современной реализации Smalltalk — Strongtalk. По собственному заявлению, он «провел слишком много времени в виртуальных Java-машинах, но так и не понял, как ими пользоваться».

Интерес Роберта к языкам программирования возник при знакомстве с Pascal — до этого момента он в основном писал на BASIC. Будучи аспирантом, он обучался у Никлауса Вирта, создателя Pascal.

Когда Роберт закончил учебу и окунулся в реальный мир индустрии профессионального программирования, то почувствовал себя так, словно совершил огромный шаг назад. Во время обучения в академии он использовал Оберон — язык, позволяющий программисту достигать очень высокой личной эффективности. Теперь же Роберт тратил свое время на размышления о языках программирования в надежде придумать способ, как ему вернуться на былой уровень производительности. После 15 лет опыта программирования на С++ он понял, что впереди у него только два пути: или продаться, или спасаться. Вместе с Робом Пайком и Кеном Томпсоном они выбрали второй вариант.

Истоки


Go начинался с четко определенной целью: разработчикам в Google требовался язык, который бы лучше подходил для их работы. У Роберта помимо этого была еще и личная цель — ему хотелось иметь в своем распоряжении небольшой, чистый, компилируемый язык с современным функционалом, который напоминал бы ему о том, к чему он привык за годы учебы. Он мечтал о «конструкторе лего» — простом и компонуемом.

Не составляло труда понять, что же не так с C++ — это был очень сложный, трудный для понимания язык, которому недоставало поддержки конкурентности, который не масштабировался и чье время сборки проектов было действительно большим. В Google стала общепринятой следующая процедура: если у кого-то возникала проблема с С++, то он отправлял email с просьбой о совете, и каждый прочитавший отвечал на него по-разному. В итоге, под конец дня гуру С++ из команды (который написал книгу по теме вопроса) давал окончательный ответ. Было очевидно, что такой процесс никуда не годился.

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

  1. Простота, безопасность, читабельность первостепенны. Однако, простота языка не означает, что созданный на нем продукт обязан быть простым — иногда это просто неосуществимое условие.
  2. Стремление к ортогональности в дизайне языка. Означало множество средств языка, которые бы не пересекались между собой и которые можно было бы проще комбинировать (combine) и составлять композиции (compose). Авторам хотелось, чтобы существовал только один путь написания конкретной конструкции.
  3. Минимализм: существует всего один способ написать некоторый кусок кода (для этого правила сейчас есть несколько исключений — например, определения переменных)
  4. Все дело в выразительности алгоритмов, а не в системе типов.
  5. Отсутствие желания приспосабливать язык к какому-либо одному способу использования (к примеру, разработчики не хотели делать написание lock-free алгоритмов суперпростым, потому что эта область интересует лишь немногих экспертов, в то время как большинство программистов на Go не должны о них беспокоиться).
  6. Коллективное осознание истории развития языков программирования.

Еще одним ключевым источником вдохновения стали две научные статьи Хоара ( C.A.R. Hoare, Hints on Programming Language Design, Communicating Sequential Processes).

Первые заметки о дизайне языка были написаны 23 сентября 2007 года; в этот день Роб вместе с другим Робом и Кеном впервые провели мозговой штурм на данную тему. Заметки, оставшиеся с того обсуждения, начинаются со следующих слов:

«Отправная точка: C. Нужно исправить некоторые очевидные недостатки языка, убрать кое-какой очевидный хлам...»

Оглядываясь назад, можно сказать, что многие из идей, пришедших в голову разработчикам в тот день, дожили до финального релиза Go:
  1. Синтаксис: нотация с ведущими ключевыми словами, большая «зачистка»
  2. Выражения: 5 уровней приоритета операций (вместо 14 в C++)
  3. Явно указанные размеры базовых типов, нет неявных преобразований
  4. Пакеты и импорты
  5. Методы с явным указанием параметра-получателя (receiver)

Многие из концептов потерялись по пути, однако, еще больше идей до воплощения не дожило. Критики Go любят замечать, что «в Go нет ничего нового», но они упускают то, что это является хорошим качеством для языка программирования. Цитируя Алана Кея, «большинство идей происходит из предшествующих идей»; Хоар в 1973 году выразился еще точнее: «Задача дизайнера языка программирования в консолидации идей, а не в инновациях».

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


История


Algol 60 (John Backus, John McCarthy, Alan Perlis, 1958-1960):
Точка отсчета для всего. Этот язык представлял собой сплав лучших идей, взятых из Fortran и Lisp. Передовой язык для своего времени. Хоар сказал про него, «Вот язык, настолько превосходящий свое время, что ему суждено стать лучшим не только относительно своих предшественников, но и потомков». В языке появились блочные структуры, вложенные и рекурсивные функции, объявления типов, инструкция «for», оператор возврата «return», разделенные точкой с запятой операторы, блоки «begin/end», «вызов по имени» и многое другое из того, что сегодня нам кажется существовавшим всегда.

Наследники Algol — C и Pascal — оказали прямое влияние на Go.

Pascal (N. Wirth, 1968-70):

BEGIN/END для блоков, точки с запятой как разделители, объявления слева-направо, структурированные типы данных, понятие предопределенных стандартных функций, создан для преподавания.

C (Ritchie, 1969-73):

Фигурные скобки для блоков, точка с запятой как признак завершения инструкции, использование мимических объявлений (объявления переменных выглядят так же, как и использования), двойственность массивов-указателей, статическая типизация со слабым принуждением, спроектирован для написания ядра Unix.

В свою очередь, Pascal вдохновил Modula и Oberon, которые оказали существенное влияние на Go.

Modula, Modula-2 (Wirth, 1978-1980):

Модули для независимой компиляции и инкапсуляции, корутины и мониторы, поддержка низкоуровневого программирования.

Oberon (Wirth, 1986):

Упрощенные модули. Попытка очистить Modula-2 до настолько маленького языка, насколько это возможно.

Object Oberon (Templ, 1989):

Диалект Оберона с классами и методами.

Oberon-2 (Wirth, 1991):

Переработанный Object Oberon; Oberon с методами на записях (структурах)

Сравните 2 сниппета кода на Oberon и на Go:

Как можно заметить, несмотря на различный синтаксис, структура остается той же. Код на Go состоит из операторов на С, однако структура выглядит как Oberon. Это неудивительно: Go использует квинтэссенцию удачных концепций языка, при этом развивая ее. Наследие Oberon проглядывает в Go в той же степени, что и наследие С за счет таких концепций, как пакеты, импорты, строгая безопасность памяти, GC и динамическая проверка типов.

Объектная ориентация и дженерики


В 90-ых на языки программирования нашло «сумасшествие» систем типов:
  1. C++, Java, другие языки
  2. сложные OO системы типов
  3. сложные системы типов для дженериков

Также в это время происходит активное распространение динамически типизированных интерпретируемых языков (Perl, Python, Lua, Javascript, Ruby, Erlang).

В 00-ых начинает набирать ход реакционное движение: выясняется, что сложный объектно-ориентированный код тяжело читается; многие жалуются на то, что с того света вернулся «спагетти-код» времен 70-ых. У многих языков излишне загроможденная нотация ( “public static void”). Кроме этого, до людей начинает доходить, что большие программы на динамически типизированных языках тяжело поддерживать.

Тем не менее, для нас было очевидным, что Go требуется некоторая поддержка объектной ориентации. В этом случае вдохновением послужил Smalltalk (Kay, Ingalls, Goldberg, 1972-80). В Smalltalk всё — объект; сообщение может быть послано любому объекту.

В Go авторам языка хотелось достичь мощи безопасности типов без использования самой системы типов. Традиционно, объекты содержат в себе информацию о типах, что привязывает типы объектов к «классам» и ограничивает ими. Ключевым озарением для авторов Go стала идея о том, что методы можно присоединять к любому типу, если интерфейсы будут нести информацию о типах вместо объектов. Как оказалось, методы и интерфейсы — это единственные дополнительные механизмы, которые необходимы для поддержки объектно-ориентированного программирования.

Конкурентность


Грамотная поддержка конкурентности планировалась с самых первых дней. Однако, по этой теме лучше может рассказать Роб Пайк, чем докладчик. Поэтому рекомендуется посмотреть доклады Пайка и почитать статьи:
  1. “Newsqueak” (Pike)
  2. “Communicating Sequential Processes” (Hoare)

Дженерики


imageДженерики — единственная серьезная возможность языка, отсутствующая в Go. Новообращенным программистам на Go часто их не хватает; однако, дженерики представляют собой скорее механизм системы типизации, от которой авторы хотели уйти, и поэтому непонятно, являются ли они действительно необходимым языковым средством.

Дженерики невероятно сложны как по своей семантике, так и по реализации; здесь есть определенные компромиссы, с которыми придется считаться: например, придется выбирать, хотите ли вы иметь бинарник большего размера, или же более медленный бинарник, или же писать больше исходного кода. Кроме того, дженерики нельзя считать ортогональным решением — они взаимодействуют с многими другими языковыми средствами и запутывают их.

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

Собирая все вместе


Спасибо Google: они предоставили авторам языка непозволительную роскошь, позволив им потратить 2 года на обкатку основ языка. Разработчики поступали принципиально: они добавляли только по одной функции языка за раз. Например, при добавления отображений ( maps), авторы языка потратили несколько недель на обдумывание их дизайна и реализации. Это помогло добиться ортогонального дизайна, к которому так стремились создатели Go.

Трое отцов-основателей (Роберт, Роб и Кен) были людьми весьма своеобразными и непохожими друг на друга; поэтому не было ничего удивительно, что их дискуссии были очень острыми и часто достигали сурового эмоционального накала. Из-за этого местами все получалось не так хорошо, как хотелось бы. Но возможность иметь в одном проекте разные точки зрения сделало финальный продукт гораздо сильнее, в то время как присоединившийся позже Расс Кокс помог убрать весь мусор и собрать все воедино. Ян Ланс Тэйлор создал вторую реализацию компилятора (gccgo), о существовании которой никто не подозревал до момента ее выхода. Это было подтверждением успешного достижения целей, заложенных в дизайне языка с самого начала. Реализации разнились по мелочи, и это серьезно помогло для отладки оригинального компилятора и спецификаций языка. Теперь у разработчиков языка есть третий фронтенд к gcc (пакет go/types), который помогает с дальнейшей проверкой компиляторов и спецификаций. Как выяснилось, иметь 3 компилятора-фронтенда для gcc — это чрезвычайно полезная штука.

Эволюция Go


Go сильно менялся с течением времени — в частности, изменялись синтаксис и семантика языка. Полезным решением стала разработка стандартной библиотеки параллельно созданию языка — так разработчики смогли убедиться, что не занимаются бессмысленным проектированием. Утилиты gofmt (для изменений в языке) и gofix (для изменений в API) сослужили разработчикам хорошую службу в тех случаях, когда их желание что-то изменить требовало серьезного рефакторинга.

В последующем в языке появились возможности, добавленные уже гораздо позже: к примеру, необязательность точек с запятой, необязательность типов для составных литералов (composite literals), необязательность границ для слайсов. При добавлении этих языковых средств разработчиков не раз выручал инструментарий, позволявший делать крупные изменения без необходимости переписывания большого количества кода вручную.

Будущее Go


Что делает язык программирования успешным?
  • Ясная цель (антипример — PL/1);
  • Комплексная и цельная реализация: язык, библиотеки, утилиты;
  • Готовность рынка. Верно выбранное время для выхода;
  • Технологический прорыв;
  • Языковые средства, отсутствующие у конкурентов. Lisp был первым языком, в котором была настоящая рекурсия и много других возможностей. Smalltalk был интерактивным, графическим, объектно-ориентированным и чрезвычайно простым.
  • Редко: маркетинг (бывает, что он помогает). Sun поставила все на Java. Java живет до сих пор, а Sun — мертва.

Что насчет Go?

  1. Авторы имели четкую цель в отношении дизайна языка
  2. Мультипарадигменность (императивный, функциональный, объектно-ориентированный)
  3. Синтаксическая легкость
  4. Средства языка, которых нет у конкурентов: горутины, интерфейсы, defer (теперь и в Swift)
  5. Утилиты, которых нет у конкурентов: быстрый компилятор, gofmt (который теперь пытаются скопировать в сообществах других языков), go build (отсутствие необходимости во внешнем описании или Makefile)
  6. Сильная стандартная библиотека
  7. Цельная реализация
  8. Отличная документация и онлайн-средства (Go playground)
  9. Отсутствие корпоративного маркетинга

Станет ли Go мейнстримом?

Нам нужно пересечь пропасть между «ранними последователями» и «ранним мейнстримом». Сообщество вокруг Go должно объединиться вокруг этой цели и не совершить слишком много ошибок на пути в будущее. Одной из таких больших ошибок будет фрагментация платформы. Среднее время признания для языка программирования составляет 10 лет.

Есть и подводные камни. Сам язык «заморожен», но другие вещи, являющийся частью «дизайна языка», постоянно изменяются:

  1. build tags и другие специальный комментарии;
  2. специальные интерпретации путей импорта;
  3. внутренние пакеты;
  4. зависимости (с которыми разработчики пролетели, если бы не помощь сообщества)

Эти механизмы не являются частью спецификации языка, поэтому со временем могут возникнут неприятные расхождения: например, начнет различаться семантика на различных платформах.

Мысли напоследок (которые не следует воспринимать слишком серьезно)


  1. В 1960, языковые эксперты из США и Европы объединили свои усилия для создания Algol 60. (Роберт замечает, что комитет состоял целиком из людей, которые непосредственно занимались дизайном и реализацией языков программирования — в отличие от наших дней, когда в составе комитетов отдельных полно хватает людей, которые никогда ничем подобным даже не занимались)
  2. В 1970, дерево Algol разделилось на ветви C и Pascal. В некотором смысле, это были американская и европейская ветви.
  3. Во многих смыслах, язык отражает личность своего создателя и его национальность. “Однажды кто-то сказал Никлаусу Вирту, что ему показалось, что его язык (Pascal) отражает страну, в которой тот родился и вырос (Швейцарию). Этому человеку казалось, что языки Вирта стремились быть маленькими и компактными, и «не слишком-то свободными» (к примеру, в сравнении с C).”
  4. 40 лет спустя, 2 ветви развития объединяются в Go. (В команде Go был один человек из США, один — из Европы, и третий — из Канады, про которого Роберт в шутку назвал «нейтральным арбитром»)

Что ж, посмотрим, получится ли у Go прожить такую же долгую жизнь, как и у его предшественников.

image

Q & A


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

A: Сейчас мы думаем о Go как об универсальном языке, языке общего назначения. Не в последнюю очередь из-за того, что люди используют его для всего подряд. Для нас имеет значение весь спектр применения языка.

Q: Можете ли вы рассказать о процессе проектирования и разработки стандартной библиотеки?

A: В начале библиотека std строилась по образу и подобию других стандартных библиотек — в частности, на нее сильно повлияла std из Plan 9. К примеру, пакет fmt был вдохновлен именно Plan 9 (хотя там использовались другие команды), но само имя fmt пришло как раз оттуда. По большей части, разработка происходила стихийно, особенно на ранних стадиях, поэтому многое лишнее в стандартной библиотеке было со временем убрано; какого-то особенного дизайна за всем этим не стояло.

Q: Автоматический подсчет ссылок или GC?

A: В начале создатели языка думали об автоматическом подсчете ссылок как о лучшем варианте. Проблема в том, что этот способ плохо сочетается с конкурентностью; возможно, в этом вопросе могут помочь lock-free алгоритмы. Разработчики общались с Дэвидом Бэконом из IBM, который создал подобный GC под названием Metronome. Проблема в том, что даже если вы все сделаете правильно, в конце концов у вас все равно могут появиться циклические ссылки; и для сбора циклических ссылок вам придется написать GC, так что мы пошли путем выбора чистого GC.

Q: Большинство разработчиков использует Go для сервер-сайда. Собираетесь ли вы сделать Go более привлекательным для клиент-сайда?

A: Согласно оригинальному дизайну, Go не был обычным универсальным языком; это был системный язык программирования для сервер-сайда. Поэтому у нас нет пакета для создания UI. Теперь, когда мы движемся в сторону универсального языка, мы хотели бы создать такой пакет, однако это очень большой объем работы, т.к. необходимо делать его кроссплатформенным. В этом вопросе нужно будет прибегнуть к помощи экспертов из коммьюнити.

Q: Были ли попытки сделать Go языком более функциональным?

A: В Go уже есть многие возможности функциональных языков: например, замыкания. Но сейчас язык «заморожен», и его создатели не хотят экспериментировать с новым дизайном — поскольку это важно для всех тех, кто сейчас уже использует язык в своей работе.

Q: Что вы думаете насчет маленьких языков, или DSL (предметно-специфичный язык)? Особенно, учитывая что Go — универсальный язык широкого профиля.

A: Авторы работали в Google над DSL под названием Sawzall. Наблюдения показали, что со временем DSL начинает использоваться не для тех целей, для которых он предназначался изначально; а когда вы начинаете добавлять в язык все новые возможности, он со временем в любом случае превращается в «большой» язык. DSL, определенно, занимают свою нишу в некоторых областях, но создатели Go не думают, что соответствующие сценарии использования соответствуют духу Go.