Хороший… Плохой… Главное — у кого ружьё!
6,4
рейтинг
11 июля 2015 в 13:13

Разработка → Эволюция Go перевод

image

Подошедший к концу 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 году выразился еще точнее: «Задача дизайнера языка программирования в консолидации идей, а не в инновациях».

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

image

История


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:

image


image


Как можно заметить, несмотря на различный синтаксис, структура остается той же. Код на 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.
Перевод: Роберт Грисмер
Владимир Маслов @HotWaterMusic
карма
166,7
рейтинг 6,4
Хороший… Плохой… Главное — у кого ружьё!
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • +4
    Спасибо, отличный доклад и качественный перевод!
  • +1
    Критики Go любят замечать, что «в Go нет ничего нового», но они упускают то, что это является хорошим качеством для языка программирования.


    Такие вещи надо, как минимум, обосновывать. А еще лучше доказывать.

    Зачем использовать язык в котором «нет ничего нового»? Только вложить деньги в обучение команды и поиметь проблемы с заменяемостью сотрудников?
    • +7
      нет «инновационных» фишек. Просто взяли best practices и объединили в ортогональном подходе.
      • 0
        Какие именно best practices вы имеете ввиду?
        • +2
          Go in a Nutshell:
          • Imperative language
          • Statically typed
          • Syntax similar to Java/C/C++, but less parantheses and no semicolons
          • Compiles to native code (no JVM)
          • No classes, but structs with methods
          • Interfaces
          • No implementation inheritance. There's type embedding, though.
          • Functions are first class citizens
          • Functions can return multiple values
          • Go has closures
          • Pointers, but not pointer arithmetic
          • Built-in concurrency primitives: Goroutines and Channels

          + https://en.wikipedia.org/wiki/Go_(programming_language)#Language_design
          • +2
            Половина есть в любом современном языке, вторая половина пунктов спорные, как минимум.
            • +4
              Половина есть в любом современном языке
              На то оно и best practices — проверено делом.
    • +2
      Обучение сотрудников — громко сказано, Go учится за неделю по докам на их же сайте (Effective Go), его стандарт (sic!) на 4 листах A4 умещается.
    • 0
      Такие вещи надо, как минимум, обосновывать. А еще лучше доказывать.

      Есть вещи, которые нельзя формально доказать — что некоторые хабражители успешно используют в качестве аргумента («не смог доказать — значит это не так»).
      Если бы Вам и вправду было интересно обоснование пользы простоты языков, Вы бы точно не пропустили сотни статей и десятки докладов на эту тему.
      • 0
        А я их и не пропустил. Вот только не увидел как эта эфимерная «простота» выливается в измеримые характеристики, типа продуктивности, плотности багов и количества строк на единицу функционала.
    • 0
      -
    • 0
      … глюк
    • 0
      Чтобы не запрещать использование «нового» и, главное, не следить за соблюдением запрета :)
  • +1
    > В Google стала общепринятой следующая процедура: если у кого-то возникала проблема с С++, то он отправлял email с просьбой о совете, и каждый прочитавший отвечал на него по-разному.

    Звучит, мягко выражаясь, странно.
    • +5
      И, тем не менее, это так. Я думаю любой, кто работает с C++ достаточно долго сталкивался с тем, ситуаций, когда он пытался решить какую-нибудь проблему много часов (а то и дней), а потом узнавал о том, что, на самом деле, она решается «легко и элегантно» — но способом, про которые 90% C++ программистов никогда и не слышали. Я, например, узнал о том, как выглядит C++ аналог calloc'а (выделение памяти под массив с его одновременным обнулением) только после многих лет работы на C++. Попробуйте сами дать правильный ответ нет раскрывая spoiler.
      Ответ спрятан тут
      char *p = new char[size](); // Две круглые скобочки превращают malloc в calloc!
      А теперь — попробуйте объяснить чем этот способ хуже, собственно, calloc'а.
      Ответ спрятан тут
      Версия с new явно обнуляет массив и тем самым делает его «грязным», calloc же ипользует mmap «под капотом», что для больших массивов заметно эффективнее.
      • –4
        Я достаточно давно работаю с С++, и никогда не имел проблем с С++. Точнее, мелкие проблемы возникают часто, и решаются либо за 5 минут гугления и чтения StackOverflow, либо за полчаса ожидания ответа на собственный вопрос на StackOverflow.
        Имхо — С++ лучший, не побоюсь даже сказать, единственный современный компилируемый объектно-ориентированный язык для кросс-платформенной разработки.
        Чем плодить языки, лучше бы мужики пошли в С++ standardization committee и улучишили язык по своему видению или, банально, дописали недостающие в std библиотеки.
        • +8
          Самая большая проблема C++ — это его невероятная сложность и ограниченная стандартная библиотека. Ну действительно — что это, чёрт побери, за кроссплатформенный язык в котором нет никакой возможности даже какую-нибудь внешнюю программу вызвать переносимым способом! Даже если boost подключить! Но это всё-таки меньшее зло: можно всё-таки довести до ума Boost.Process, добавить всяких библиотек для работы gzip'ы/bzip'ы… но всё это не нужно, так как на фоне изучения собственно C++ вопрос «а как подключить libbzip» кажется уже мелочью.

          И вот со сложностью можно бороться только одним способом: созданием нового языка. К сожалению опора на GC обозначает, что заменить C/C++ Go, увы, не сможет. Сможет ли rust — не знаю, время покажет, но Go — точно не сможет.
          • –6
            Какая «невероятная сложность»? Да, есть нюансы, на изучение языка нужно потратить некоторое время. Но я бы не назвал С++ сложным. Он объёмный (много фич), но понятный.
            • +7
              >Он объёмный (много фич), но понятный.
              Если вы считаете С++ простым и понятным, то значит вы не писали ни на чем другом кроме него.
              • +1
                Писал на Delphi, C#, Java. Немножко на Erlang :)
            • +6
              Он объёмный (много фич), но понятный.
              «Много фич» — это и значит «непонятный».

              У любого языка есть две сложности:
              1. Сложность написания программ на этом языке.
              2. Сложность чтения программ на этом языке.
              И у них зависимость — едва ли не противоположная (чем богаче и выразительнее язык тем проще на нём писать, но и тем сложнее разбираться в хитромудростях написанного), и если не уточнять, то зачастую люди не работавшие над большими проектами (где количество кода меряется в миллионах строк) под термином «сложность языка» подразумевают именно первое. Потому что когда на проекте один человек (или несколько людей с один и тем же background'ом), то никого не напрягает сложность языка: вы просто используете его небольшое подмножество, которое вам нравится, а всё разнообразие его «чудес» вас не касается.

              А вот когда над проектом начинает работать множество людей с разным образованием — вот тогда всё и начинается. Я это очень хорошо ощутил, когда мой коллега посмотрел на мой код, который я прислал ему на ревью, почти квадратными глазами. А там всего-то навсего использовался using в классе. И даже несмотря на то, что такое использование прямо разрешено в style guide код пришлось переписывать, чтобы другие разработчики от него не офигели. На больших проектах под сложность языка всегда подразумевается именно сложность чтения и понимания чужого кода.

              И вот тут у C++ всё плохо. Контрольные вопросы: вы-то сами с кодом, подобным тому, который начал дискуссию, сталкивались? Смогли бы понять, что там происходит? А понять как вынос функции-друга описанной в теле класса (да-да, это возможно) из него наружу может сломать программу? Не просто привести к ошибке компиляции, а привести к тому, что всё упадёт при запуске? Вот этот вот простой пример в «простом» языке можете разобрать:
              Маленький примерчик
              $ cat test.cc
              #include <iostream>
              
              #ifndef IN_CLASS_DECLARATION
              #define IN_CLASS_DECLARATION 1
              #endif
              
              #define FOO_FUNCTION \
                void foo(const cls<T> &a, T b) { \
                 std::cout << "foo(T):" << a.t << " " << b << std::endl; \
                }
              
              template<typename T>
              class cls {
               public:
                cls(T t_) {
                 t = t_;
                }
                operator T() {
                 return t;
                }
              #if IN_CLASS_DECLARATION
                friend FOO_FUNCTION
              #endif
                T t;
              };
              
              #if !IN_CLASS_DECLARATION
              template<typename T> FOO_FUNCTION
              #endif
              
              void foo(double, double) {
                std::cout << "oops" << std::endl;
              }
              
              int main() {
                cls<double> a = 1;
                foo(a, 1);
              }
              $ g++ -DIN_CLASS_DECLARATION=1 test.cc -o test-in
              $ g++ -DIN_CLASS_DECLARATION=0 test.cc -o test-out
              $ ./test-in
              foo(T):1 1
              $ ./test-out
              oops
              
              а в реальной прогрмме это была не одна функции foo, это был operator, который, понятно, взаимодействовал с кучей других operatorов из других включаемых файлов.

              Это когда у вас в библиотеке много фич, вы можете сказать «она простая, но объёмная». Потому что любое использование этих фич видимо в коде. А вот использование фич языка в коде зачастую либо вообще невидимо, либо выглядит настолько невинно и незаметно, что не зная о них вы можете запросто порушить код и потом потратить неделю на его отладку. Продолжая пример выше: если у вас возле какого-нибудь невинного "T *p=new T();" не стоит комментарий, что скобочки тут не просто так, а по делу, то кто-нибудь может их и убрать — и компилятор ему слова дурного не скажет, а падать ваша программа начнёт самым странным и непредсказуемым способом.
              • 0
                Я не спорю, что на С++ можно написать никому не понятный код. Очень даже можно. Соглашусь, пожалуй, что в этом смысле язык сложный. Но сложный код писать не нужно. *Почти* любую задачу можно решить простым, красивым и понятным кодом. Например, new я уже 100 лет в обед как не использую.
                • +1
                  Разговоры про «сложный код писать не нужно» вы можете вести, опять-таки, только в маленькой компании. А в большой — подразделения имеют разную историю, используют библиотеки купленные у разных поставщиков и т.д. и т.п.

                  Да и в примерах выше — речь-то идёт о довольно-таки банальных вещах. Вы можете сколько угодно хвалиться тем, что вы «100 лет в обед как не используете» new, но в один прекрасный момент вам это потребуется (ну, скажем, чтобы два процесса могли общаться через shared-memory) и вот тут-то у вас проблем возникнет — мало не покажется.
                  • 0
                    Другими словами, проблема не в языке, а в программистах. Ч. т. д. И нет, мне не понадобится new.
                    • +5
                      Нет, «проблема в программистах» может быть в случае, когда у вас нет выбора языка. Если вы работаете в команде, где вам ставят жесткие условия на чём писать, или вы студент, которому нужно делать лабораторную именно на этом языке — тогда да, ваше утверждение справедливо.

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

                      Поэтому дело именно в языке. «Добавочная сложность», которую привносит С++ в разработку для вас не кажется «сложностью», просто потому что вы к ней привыкли. В других языках, с которыми вы работали дела обстояли не намного лучше, поэтому это для вас некий baseline, это «норма». Но есть другие языки, которые вы не знаете (и, наверняка, не хотите по ряду вполне резонных причин), для которых эта «норма» давным давно уже неактуальна, в них уже своя норма, и, ценой компромиссов, «добавочная сложность» этих языков намного меньше.

                      И именно поэтому для многих людей, которые имеют возможность сравнивать, С++ уже однозначно является «переусложненным языком», причем сложность которого себя не окупает. А такие языки, как Go эти компромиссы ещё более уменьшают — настолько, что ниша С++ становится совсем уже узкой, да и ту Rust отбирает.

                      Извините, если где задел — я лишь пытаюсь подобрать правильные слова, чтобы помочь вам понять, почему столько людей (и я не только про Хабр, а про тех же авторов Go и Google) считают С++ «неоправданно сложным языком», а вы с этим не согласны.
                      • –6
                        Я думаю, что понимаю, о чём вы говорите, и согласен лишь отчасти.
                        Да, я действительно *пока что* не хочу изучать все эти Go, Rust, D и ещё полтора десятка непонятно кому нужных экзотических языков. Меня устраивает язык, которым я сейчас пользуюсь 95% времени, потому что считаю его лучшим из существующих на данный момент. И уж точно не считаю его сложным.

                        Более того, я надеюсь, что эти языки не взлетят и тихо загнутся. Потому что в этой сфере предпочтительна монополия. Проблема языков, как правило, не в самом языке, а в библиотеках для него. Чем больше библиотек, тем проще найти нужную и прикрутить в своё приложение вместо того, чтобы тратить время на имплементацию велосипеда, который уже писали тысячи человек до того… на других языках. Я хочу писать уникальную функциональность, а не очередную обёртку над threading API / библиотку для работы с UTF-8 строками / ещё какую-то фигню, которая сжирает кучу времени и без которой нельзя нормально реализовать высокоуровневую логику (можно ещё вспомнить ваш пример с вызовом внешнего процесса). А обилие языков только распыляет усилия программистов.
                        • +4
                          Так вас никто и не заставляет. Если вам комфортно с С++, ваши задачи, временные рамки, коллеги и условия позволяют спокойно его использовать — в чём проблема?

                          … не хочу изучать все эти Go, Rust, D и ещё полтора десятка… Меня устраивает язык, которым я сейчас пользуюсь 95% времени, потому что считаю его лучшим из существующих на данный момент.

                          Отсюда и конфликт в дискуссиях. Я, к примеру, писал долгое время на С++, и уже 2.5 года пишу на Go, поэтому могу объективно сравнить оба языка. Вы же, из этих двух языках, на практике знаете лишь один, но считаете его лучше. Это необъективно. Любая ваша аргументация поэтому обречена быть чисто теоретической, а споры бессмысленными.
                          Ну как можно доказывать, что инструмент А, лучше инструмента Б, если вы никогда не держали в руке Б?

                          Чем больше библиотек, тем проще найти нужную и прикрутить в своё приложение вместо того, чтобы тратить время на имплементацию велосипеда, который уже писали тысячи человек до того… на других языках. Я хочу писать уникальную функциональность, а не очередную обёртку над threading API / библиотку для работы с UTF-8 строками / ещё какую-то фигню, которая сжирает кучу времени и без которой нельзя нормально реализовать высокоуровневую логику

                          Кстати, насчет «найти и прикрутить библиотеку» — в С++ этот процесс занимает в лучшем случае десятки минут (и то, если уже раньше с этой библиотекой работал) — найти, скачать, собрать, установить, прописать нужные пути импортов и линкера, разобраться в документации (которая у каждой либы по своему сделана), добавить код. И это редко работает гладко, обычно это возня на целый вечер.
                          В Go простота этого процесса доведена до предела и занимает секунды: одна команда «go get url-проекта» и одна строчка «import url-проекта» в коде. При этом все существующие библиотеки ищутся централизованно через godoc.org и там же всегда доступна стандартная форма документации, которая есть у подавляющего большинства библиотек в Go.
                          И если в С++ задача «попробовать 5 разных библиотек» занимала несколько дней, то в Go занимает несколько минут.
                          И так во многих других аспектах.
                          Ирония в том, что вы вроде бы понимаете, что хотите «писать уникальную функциональность», а не тратить время на возню с библиотеками и обертками. Но это именно то, на что в С++ уходит чересчур много времени.
                          И, да, UTF8, фантастическая конкурентность (threading API это жалкое подобие) и 90% «еще какой-то фигни», наличием библиотек для которых вы хвалитесь в С++ — в Go идёт из коробки, и очень хорошо продуманы. По факту, то, что есть в стандартной библиотеке Go достаточно для 80% современного системных/серверных софта.

                          Вобщем, мой поинт в том, что только поработав, и сравнив на личном опыте оба языка, вы сможете объективно их оценивать. Не раньше.

                          Более того, я надеюсь, что эти языки не взлетят и тихо загнутся.

                          Ну тут без комментариев )
                          • 0
                            Я видел синтаксис инструмента В. А также С и D. Спасибо, не надо, я увидел достаточно. Уберите эти орудия пыток.

                            > В Go простота этого процесса доведена до предела и занимает секунды: одна команда «go get url-проекта» и одна строчка «import url-проекта» в коде.

                            Я думаю, это компромисс удобство(=жётская стандартизация) / гибкость (=бардак, каждый творит, что хочет). Go идёт по пути удобства, С++ — гибкости. Кажому своё. Лёгкость подключения библиотек — это классно, но их кто-то ещё должен сначала написать.

                            >Вобщем, мой поинт в том, что только поработав, и сравнив на личном опыте оба языка, вы сможете объективно их оценивать. Не раньше.

                            Да, это так, не буду спорить. Но выше я уже написал, что конкретно Go мне не нравится.
                          • –1
                            > В Go простота этого процесса доведена до предела и занимает секунды: одна команда «go get url-проекта» и одна строчка «import url-проекта» в коде.

                            Ага. А потом, внезапно, приходит понимание, что pin to master — не лучший способ управления зависимостями, и приходится «прикручивать библиотеки». То есть, фактически, go в этом плане ничего не решает — он просто откладывает решение проблемы.
              • 0
                «Много фич» — это и значит «непонятный».

                Вот только «Мало фич» вовсе не означает «Понятный». Но про это что-то все забывают.

                Есть языки с предельно малыми количествами языковых фич — brainfuck и lisp (scheme). Оба не отличаются понятностью. Хотя scheme при этом намного мощнее того же Go.
                • +1
                  Go разрабатывался с целью, чтобы код выглядел максимально и читался легко. Это так и есть, судя по личным ощущениям и отзывам различных компаний, которые не боятся показывать куски кода менеджерам и даже те понимают, что нельзя сказать про Ruby, одно время который пытались преподавать аж детям.
                  • –2
                    Читать код — навык который можно и нужно тренировать. От языка этот навык не зависит. Запутать код можно вообще на любом языке. Почти на любом языке можно код превратить в подобие eDSL, который будет читаться даже неспециалистом.

                    Мне, например, каналы и слайсы в go рвут мозг. Мешанина значков, которые никак не могу запомнить как работают. Я бы предпочел библиотечные функции вместо этой хрени.
                    А еще бесит отсутствие комбинаторов\list comprehensions или вообще любых декларативных способов обработки списков. Любой более-менее сложный код превращается в тучу вложенных циклов.
                    • 0
                      Мешанина значков, которые никак не могу запомнить как работают.

                      Ровно два значка:
                      <- = стрелочка «данные идут справа налево»
                      [] = это квадратные скобочки

                      Какой именно из этих целых двух значков вам тут рвёт мозг?
                      • –1
                        Вот только смысл значка зависит от того где значок написан. Это и бесит. Кстати в других языках подобного идиотизма нет.
                        • 0
                          Ну да, аж целых два use-кейса:
                          item := <- myChannel
                          

                          и
                          myChannel <- item
                          


                          Действительно, мозговзрывающе и контринтуитивно. В других языках такой сложности нет.
                          • +2
                            А про описания типов забыл?

                            Ну и
                            := <-
                            

                            Это пять. Даже в хаскелях таких вещей не встретишь.
                            • 0
                              Мне тоже первый раз это показалось вырвиглазым.
                              Зато все понятно и нельзя понять двояко: объявить переменную нужного типа и присвоить ей то, что вычитаешь из канала.
            • –7
              2 минусатора не осилили С++? Забавно.
        • +5
          Я достаточно давно работаю с С++, и никогда не имел проблем с С++

          Но их имели программисты всего мира, включая программистов Google. Будь С++ таким, как вы его хотите видеть, не появился бы ни Go, ни Rust, все бы продолжали писать на С++, не сомневайтесь.
  • +1
    Отличный доклад, но «интерпретируемый эрланг»…
    • +1
      Ну, будем честны — erlang компилируется в байткод, который и вправду интерпретируется. Ни JIT, ни AOT нет.
      Впрочем, все знают — erlang выбирают не за raw performance.

  • 0
    А что у Go с протоколами типа SOAP, как он работает с XML, есть ли библиотеки работы с SMTP, HTTP? В общем, есть ли свой 'CPAN'?
    • +3
      В стандартной библиотеке есть средства для работы, как с HTPP, так и с SMTP
    • +2
      > как он работает с XML

      В стандартной библиотеке есть поддержка маршаллинга аннотированных структур в xml и обратно.

      > SMTP, HTTP

      Клиент SMTP и сервер/клиент HTTP есть, опять же, в стандартной библиотеке.

      > В общем, есть ли свой 'CPAN'?

      CPAN-ом для go фактически является github. В проект можно импортировать напрямую из git/svn/hg репозиотриев (есть батарейки для популярных хостингов). Из минусов, пожалуй, есть лишь отсутствие встроенной поддержки указания конкретных тегов/бранчей для git реп, т.е. всегда берется последняя ревизия из дефолтной ветки, но это частично решается внешними костылями, вроде, gopkg.in.
      • 0
        А SOAP? Можно ли построить на нём сервер и клиент, с полной функциональностью 1 и 2, есть ли несовместимости с другими серверами/клиентами?
        • 0
          SOAP нынче стоит в одном ряду со словами «dead», «masocоstic» и «zombie», поэтому в стандартной библиотеке, конечно же его нет.
          Посмотрите вот, какие-то пакеты есть, но более детально не подскажу: godoc.org/?q=soap

          Вот еще статья на тему: taoofmac.com/space/blog/2014/05/11/1121
        • 0
          Я немного возился с SOAP и клиентом при помощи стандартных библиотек в ноябре прошлого года, это было не самое приятное занятие (особенно если сравнивать по удобству с SoapClient в PHP), однако вроде работало. Что там сейчас — надо смотреть, возможно с библиотеками стало удобнее.
          • 0
            Вопрос в полной поддержке SOAP сервера и клиента — схема, WS сервисы и так далее, совместимость с другими SOAP серверами и клиентами. И не только клиента, но и сервера. То есть можно ли на Go писать реальные сервера, которые будут удовлетворять стандартам.
            То, что лежит на гитхабе, выглядит непригодным.
            • –1
              То есть можно ли на Go писать реальные сервера, которые будут удовлетворять стандартам.

              Можно. Но SOAP и «реальные сервера» это не синонимы, как может показаться из вашего ответа.

              PS. readwrite.com/2011/05/26/soap-is-not-dead---its-undead
              • +2
                Я знаю один пример создания системы из десятков SOAP серверов, сделанных десятками программистских коллективов из разных стран, которые работают совместно и работают хорошо. Я не представляю, на какой другой технологии может быть реализовано подобное.
                Все высеры типа «SOAP undead zombie» стоят столько же, сколько стоят любые высеры типа «99% веб работает на PHP, значит всё остальное зомби», то есть нисколько не стоят.
                • +1
                  Я не представляю, на какой другой технологии может быть реализовано подобное.
                  ProtoBuf и его развитие Cap'n Proto отлично справятся. Правда в C++. Как оно там в Go — не знаю.
                  • +1
                    Ну протобаф в Go по понятным причинам поддерживается великолепно, Cap'n'Proto для Go есть на офсайте.
                • –1
                  У меня всегда было ощущение, что SOAP хорошо живет только в proprietary-системах, причем в экосистеме Microsoft, и это была одна из причин, почему REST победил. Я заблуждаюсь?
                  • +2
                    Конечно заблуждаетесь. SOAP и REST настолько разные, что их нельзя сравнивать — нет пересекающихся категорий. И уж тем более нет никаких оснований для «только в proprietary-системах, причем в экосистеме Microsoft». REST победил там, где не осилили все прелести SOAP и где непринципиально получить или не получить ответ.

                    Но это не важно. Go — это язык прикладной. В нём должны быть средства для написания программ.
                    • 0
                      Согласен. но Go всё таки в равной степени детище как Google, так и open-source сообщества, и если SOAP так востребован, важен и популярен, то нелогично, что нет достойных реализаций. Под любые другие протоколы, технологии и экосистемы, востребованные и популярные в это время — всё есть, если не в стандартной библиотеке, то third party. Тут же спрос решает в конце-концов.
                      Так что, либо я не так уж заблуждаюсь насчёт SOAP, либо есть иное объяснение. Но интересно, спасибо за мнение, постараюсь углубиться в тему чуть глубже.
                      • +1
                        Я посмотрел ещё в сторону анализа почтовых форматов хранения — та же история. net.mail в стандартной библиотеке очень простая библиотека, почтовые форматы поддерживает на уровне выбрать Subjеct. Смотрю godoc.org/github.com/luksen/maildir — и всё время вопрос: а где примеры? Как переместить письмо в другой фолдер (в контексте Maildir), что понимает автор вообще под Maildir? После перлового Mail::Box perl.overmeer.net/mailbox/html/index.html такие библиотеки и такая документация вызывают странное ощущение — то ли я идиот, то-ли автор библиотеки делал что-то лично для себя.

                        То есть куда я ни глянул под свои задачи — Go не имеет внятного решения. И не имеет не только решения, но и внятного объяснения откуда брать библиотеки, как они сочетаются друг с другом, как вообще сформировать набор совместимых библиотек. За что ругают Руби — надо под каждый проект собирать файлы нужных вверсий. Так в нём это хотя бы возможно. А какие механизмы для Go? Лазить в чужие репозитории Git? А в них есть эти зависимости?

                        Какие вообще задачи Go способен решить? Не имея библиотек?
                        • +1
                          Типичные детские болезни. Лет через 5 будет всё как у взрослых :-)
                          • +3
                            Конечно, количество библиотек будет расти.

                            Но я считаю неадекватной оценку «Не имея библиотек» из-за того, что человеку нужно работать с устаревшими и непопулярными вещами. Здесь всё решает спрос, в конце концов, и для всего, что сейчас в современной экосистеме сетевого/облачного софта востребовано, в Go великолепная поддержка.

                            Ну и я уже не говорю, что Go — это язык программирования, в конце концов, и библиотеки пишутся такими же людьми, у которых возникла надобность в том или ином решении. Если msgpack востребован, то под него и куча решений, а разбор Maildir — вероятно не такая частая задача в наши дни, и, если вам не подошла библиотека («где примеры?») — всегда можно написать свою, с примерами.
                            • +1
                              Это SOAP непопулярен и устарел? Только потому, что SOAP занимает 17% от RPC интерфейсов? Тогда GO устарел, так как он занимает ничтожный процент от любого языка из десятки-сотни ведущих языков?

                              Это форматы почтовых файлов и электронная почта устарела? Разбор Maildir это нечастая задача?
                              Что, в экосистеме облачного софта электронной почты больше нет?

                              Отсутствие в Go библиотек может быть по нескольким причинам. Например, 1) не успели написать, 2) не пишут на Go сложные ответственные программы так как непопулярен он пока. Но мой последний параграф

                              «То есть куда я ни глянул под свои задачи — Go не имеет внятного решения. И не имеет не только решения, но и внятного объяснения откуда брать библиотеки, как они сочетаются друг с другом, как вообще сформировать набор совместимых библиотек. За что ругают Руби — надо под каждый проект собирать файлы нужных вверсий. Так в нём это хотя бы возможно. А какие механизмы для Go? Лазить в чужие репозитории Git? А в них есть эти зависимости?»

                              как можно оправдать облачными сервисами? Как оправдать отсутствие документации даже для стандартных библиотек?
                              • +2
                                Смотрите, есть ситуация — Google, Dropbox, Docker, StackOverflow и куча других компаний, плюс еще тысячи людей успешно пишут проекты на Go, и говорят, что Go давным давно прошел стадию «нехватки библиотек».
                                У вас есть юз-кейс, где для одной спорной технологии вам не хватает одной библиотеки. Вы приплетаете сюда ещё вторую библиотеку для тоже весьма спорной по популярности задачи, в которой вам не нравится отсутствие примеров, и называете это «в Go нет библиотек»?

                                Давай быть объективными. Конечно, в Perl-е больше библиотек для устаревших или устаревающих технологий и задач. Но в Go их больше для новых и современных, для работы в облаке, для работы с современными форматами и протоколами, клиентов для новых серверных технологий и решений.

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

                                PS. Я не хочу спорить про SOAP. Но я уверен, что Google Trends не врут (и это совпадает с моим пониманием востребованности), и что, будь SOAP так популярен — выбор библиотек был у вас такой же, как для других современных протоколов/технологий.
            • +2
              Увы, пакета net/soap нет в стандартной библиотеке, и по всей видимости вряд ли он когда-нибудь появится. Встроенного клиента/сервера нет. Поэтому о полной поддержке речи не идет точно. Остаются проекты пользователей неизвестного качества.

              В стандартной библиотеке нет поддержки WSDL, поэтому приходится вручную описывать структуры. Полузаброшенные костыли на Github вроде этого и этого (посвежее) умеют генерить из XSD/WSDL .go-файлы со структурами. Для валидации по схеме ничего нет — этот вопрос время от времени возникал в рассылке go-nuts и оставался без ответов (предлагали взять биндинги к libxml2 и написать самостоятельно). Про совместимость ничего не скажу, предполагается, что придется генерить запросы руками и разбирать их также.

              В основном гуглятся только простые примеры вроде такого. Полезный опыт описан здесь и здесь, по второй ссылке приведен более сложный и интересный пример вместе с жалобами на трудности работы с encoding/xml.

              Я эксперименты с SOAP в Go прекратил, поэтому точнее не подскажу. Сам интереса ради хочу попробовать чистый C + gSOAP/cSOAP.
  • +1
    Там были и более интересные и практичные доклады :) Так, например, доклад от нашего соотечественника — Димы Вьюкова про различные утилиты для Go.
    • +2
      Год назад я заинтересовался Go как раз после замечательного доклада Дмитрия на DevConf — помнится, в том числе чатик он там писал за пять минут. В результате я увлекся историей развития ЯП (и вещами вроде Plan 9), их более современными фичами и алгоритмами, поэтому мне близко было именно это выступление. По-моему, до трех ночи я не засиживался уже месяца три, тем более в пятницу вечером и за переводом.

      Честно говоря, было бы лучше, если бы Дмитрий сам опубликовал пост по теме на Хабре — для меня как-то странно было бы переводить с английского на русский то, что изначально было на русском :) В любом случае, насколько мне известно, перевод его выступления должен скоро появиться на Хабре.
      • +1
        Про историю go ещё вот этот вот доклад был: www.gophercon.com/talks/roots-of-go
        Правда, на мой взгляд, он был очень скучный и унылый. Тот, который в этом посте — интереснее.

        А Дима вообще красавчик, да :)
  • +2
    Меня одного напрягает обилие восхваляющих Go статей, без единой строчки кода?

    Ну вот, например, ООП. Насколько я понял в Go реализована структурная типизация. Как в Go решена проблема структурной типизации вида: есть два различных интерфейса с похожими с точностью до сигнатуры методами, как реализовать оба интерфейса, если реализации их должны быть различны?

    Или вот про дженерики пишут «здесь есть определенные компромиссы, с которыми придется считаться: например, придется выбирать, хотите ли вы иметь бинарник большего размера, или же более медленный бинарник, или же писать больше исходного кода.». Отказ от дженериков — это как раз третий вариант «писать больше исходного кода», после чего необходимыми становятся всякие gofmt/gofix для обуздания копипасты.

    • +2
      Меня одного напрягает обилие восхваляющих Go статей, без единой строчки кода?

      Цель этих статей — обратить внимание людей, не знакомых с Go, показать что такой язык есть, его плюсы и минусы. Более технических статей полно в англоязычном сегменте интернета, и пока что при выборе «что переводить» приоритет больше на статьи общего плана.

      Как в Go решена проблема структурной типизации вида: есть два различных интерфейса с похожими с точностью до сигнатуры методами, как реализовать оба интерфейса, если реализации их должны быть различны?

      В Go нет «проблемы структурной типизации». Интерфейс — это описание поведения, а не реализации. Если два интерфейса содержат идентичные методы, то это два одинаковых интерфейса.
      Но, возможно, я не совсем верно понял ваш вопрос, тогда приведите практический пример, постараюсь ответить точнее.
      • 0
        Подобные статьи общего плана можно написать и про brainfuck. Без конкретных примеров, где были бы видны преимущества, такие маркетинговые статьи «для привлечения внимания» ничего не стоят.

        Наглядный пример (не на Go):

        interface XMLSerializable {
            serialize() : String
        }
        
        interface JSONSerializable {
            serialize() : String
        }
        
        class Human {
            serialize() {
                return "human name=Jim"
            }
        }
        
        function createJSONResponse( obj : JSONSerializable ) {
            return new JSONResponse( obj.serialize() )
        }
        
        createJSONResponse( new Human )
        
        

        • +1
          Подобные статьи общего плана можно написать и про brainfuck. Без конкретных примеров, где были бы видны преимущества, такие маркетинговые статьи «для привлечения внимания» ничего не стоят.

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

          Ваш пример, наверное, не самый удачный, потому что эта задача (сериализации) в Go красиво решается с помощью тегов структур и стандартной библиотеки.
          В пакетах encoding/json и encoding/xml есть интерфейсы Marshaller — json.Marshaller и xml.Marshaller соответственно. Но у них разные сигнатуры, MarshalJSON и MarshalXML. Любой тип, который хочет переопределить сериализацию в JSON или в XML — просто добавляет методы MarshalJSON и MarshalXML соответственно.
          Вместо:
          type Human struct {
              Name string `json:"name"`
          }
          
          h := Human{Name:"Jim"}
          data, err := json.Marshal(h)
          

          play.golang.org/p/RzO2LWI8dV

          делается что-то вроде
          // MarshalJSON implements Marshaller for Human
          func (h Human) MarshalJSON() ([]byte, error) {
          	return []byte(`{"human name": "` + h.Name + `"}`), nil
          }
          

          play.golang.org/p/tKxJ2OlEYj

          Но, опять же, в реальной практике в этом обычно нет нужды. К примеру, данный сниппет легко решается изменением тэга json (или xml) для соответствующего поля структуры:
          type Human struct {
          	Name string `json:"human name", xml:"HumanName"`
          }
          

          play.golang.org/p/L-zHKlQEJs
          • –1
            Мой пример наглядно демонстрирует проблему структурной типизации. Постарайтесь абстрагироваться от конкретных интерфейсов, каждый из которых может быть описан в отдельном модуле и вообще не знать о существовании друг друга. Наличие одинаковых сигнатур в разных интерфейсах не говорит о том, что функции эти делают одно и то же.
            • +3
              Постарайтесь абстрагироваться от конкретных интерфейсов, каждый из которых может быть описан в отдельном модуле и вообще не знать о существовании друг друга.

              Боюсь, что вы сейчас пытаетесь загнать себя (и меня) в логическую ловушку. Этой проблемы нет в Go by design, и вы просите решения несуществующей проблемы.

              В спеке Go четко написано:
              Two interface types are identical if they have the same set of methods with the same names and identical function types. Lower-case method names from different packages are always different. The order of the methods is irrelevant.


              В Go, интерфейс это буквально «описание поведения», и если два интерфейса содержат одинаковые методы, то это одинаковые интерфейсы.

              Взяв кальку с вашего примера:
              type XMLSerializer interface {
                  serialize()
              }
              type JSONSerializer interface {
                  serialize()
              }
              

              Это два одинаковых интерфейса.
              Любой тип, который реализует serialize() будет удовлетворять оба интерфейса, но это бессмысленно в Go, потому что интерфейс описывает поведение («сериализовать»), а не конкретную реализацию. Нужен JSON — создаете type Json struct{} и определяете serialize() для него. Нужен XML — создаете новый тип type XML struct{} и пишете свой serialize(). Создавать два интерфейса в этом примере бессмысленно. Тогда уже больше подойдет единый type Serializer interface.
              • –1
                Всё ясно. Если закрывать на проблему глаза, то её как бы и не существует.

                Это два одинаковых интерфейса.

                В том-то и беда. Логически это два разных интерфейса. Структурно же они идентичны, что вводит компилятор в заблуждение об их эквивалентности. И с ростом приложений эти любезно расставленные грабли начнут стрелять. Приятной вам отладки :-)
                • +1
                  Структурно же они идентичны, что вводит компилятор в заблуждение об их эквивалентности.

                  Не вводит, потому что так код не пишется в Go. Вы переносите паттерны с других языков с другой системой типов, и теоретизируете о несуществующей проблеме, серьезно.
                  • 0
                    Ну так баги никто специально не пишет. Они сами появляются когда где-то что-то кто-то не доглядит.
                    • +1
                      Но это совершенно не имеет никакого отношения к описанной вами проблеме. Устроить коллизию имён можно в любом языке.
                      • 0
                        В том же D я затрудняюсь сказать как устроить коллизию так, чтобы компилятор не ругнулся на это и не потребовал явным образом эту коллизию разрешить.
                • +2
                  Вы никак не можете понять, что проблема исскусственная. У вас нет тут двух интерфейсов с одинаковыми сигнатурами. У вас тут два разных интерфейса: в одном есть функция XMLSerializer::serialize(), в другом — совсем другая (хотя, может быть, и похожая) функция JSONSerializer::serialize(). У них одинаковые входные/выходные аргументы и даже часть названия, но это разные функции. Если вы будете в своей документации ссылаться на обе как на serialize(), то никто ничего не поймёт.

                  Просто в некоторых языках принято выносить часть названия функции в название интерфейса, что позволяет кое-где ссылаться на эти функции не по полному имени, а по сокращённому, вот и всё. Разработчики Go решили, что это «упрощение» таковым не является и от этой практики отказались. Всё. Где вы видите проблему?
                  • +1
                    Этот serialize похоже никак не даёт вам покоя :-) Ок, попробуем так:

                    // Интерфейс человеков
                    type Human interface {
                    	Run()
                    }
                    
                    // Интерфейс просессов операционной системы
                    type Process interface {
                    	Run()
                    }
                    
                    // Класс весёлых персонажей
                    type Person struct {
                    	Name    string
                    }
                    func (c *Person) Run() {
                    	fmt.Println("Hello, my name is ", c.Name , " and i am running")
                    }
                    
                    // Класс разрушительных процессов
                    type WorldDestroyer struct {
                    	Name    string
                    }
                    func (c *WorldDestroyer) Run() {
                    	Destroy( &World{Name: c.Name} )
                    }
                    
                    // функция для игры с весёлыми персонажами
                    func playInFunnyGames(h1 Human, h2 Human) {
                    	h1.Run()
                    	h2.Run()
                            fmt.Println("Innocent game complete")
                    }
                    
                    // Создали первого персонажа
                    p1 := &Person{Name: "Jim"}
                    
                    // Ой, где-то ошиблись и в переменной оказался объект совсем не того класса
                    p2 := &WorldDestroyer{Name: "Lucky"}
                    
                    // Молодёжная комедия превращается в фильм-катастрофу
                    playInFunnyGames( p1 , p2 )
                    
                    • +1
                      Это вам не даёт покоя высосанная из пальца проблема.

                      // Интерфейс человеков
                      type Human interface {
                      	Run()
                      }
                      
                      Здесь вы написали буквально следующее: человек — это такая хрень, которая умеет бегать. То есть лошадь — это у нас человек, процесс — это тоже человек, и вообще — куча вещей, которые явно людьми не являются — всё человеки. Вам не кажется это… хмм… несколько странным?

                      Ну да ладно. Пусть у нас человек — это тот, кто бегает. И лошадь тоже. И процесс. Ну вот такой у нас ограниченный мир. Ладно. В чём же проблема?

                      
                        // Ой, где-то ошиблись и в переменной оказался объект совсем не того класса
                        p2 := &WorldDestroyer{Name: "Lucky"}
                      
                      Оказывается проблема у нас не в том, что у нас интерфейс Human неотличим от интерфейса Process, а в том, что ошибки определённого класса у нас не отлавливаются. Ну да. А кто сказал, что язык обязан такие ошибки отлавливать? На практике эта проблема «случайно» не возникает.

                      Вернее возникает — но не совсем эта. На практике я гораздо чаще наблюдаю ситуации когда объекты реализовывающие одинаковые интерфейсы путаются (засунули «задачу» не в тот «пул задач», положили «персонажей» не в ту «главу»), чем когда вы «случайно» засовываете куда-то не туда объект из совсем другой оперы. Так стоит ли переживать из-за того, что в Go у вас механизм, который решал 1% (ну 5%, от силы — 10%) проблемы перестал действовать? Зато он же и перестал требовать костылей в случаях, которые у вас в «нормальных» языках разруливаются с большим трудом.
                      • +1
                        Ну да ладно. Пусть у нас человек — это тот, кто бегает. И лошадь тоже. И процесс. Ну вот такой у нас ограниченный мир.
                        Интересно, как вы без помощи номинативной типизации сможете отличить человека от, например, обезьяны или андроида. Дайте догадаюсь, введёте уникальный для каждого вида метод типа такого:

                        type Human interface {
                            iAmHuman()
                            Run()
                        }
                        type Monkey interface {
                            iAmMonkey()
                            Run()
                        }
                        

                        И получите эмуляцию номинативной типизации.

                        Оказывается проблема у нас не в том, что у нас интерфейс Human неотличим от интерфейса Process, а в том, что ошибки определённого класса у нас не отлавливаются. Ну да.
                        И не отлавливаются они потому, что эти два интерфейса не отличимы. Наконец-то вы признали эту проблему :-) Теперь о следующей:
                        1. мы подключаем две библиотеки из ортогональных областей
                        2. мы создаём свой кдласс, объекты которого хотелось бы использовать и с одной и с другой библиотекой
                        3. первая библиотека в интерфейсе указала, что этот объект должен реализовывать метод getId(), который должен возвращать строку удовлетворяющую некоторым ограничениям: он должен состоять исключительно из цифробуквенных символов
                        4. вторая библиотека в интерфейсе указала, что этот объект должен реализовывать метод getId(), который должен возвращать валидный идентификатор в БД вида "#\d+:\d+"
                        5. первый идентификатор можно получить из второго не сложными преобразованиями над строками (base32 например)
                        6. как в одном объекте реализовать оба варианта getId, чтобы один и тот же объект можно было использовать с обеими библиотеками?

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

                        Так стоит ли переживать из-за того, что в Go у вас механизм, который решал 1% (ну 5%, от силы — 10%) проблемы перестал действовать?
                        Перила пригождаются гораздо реже 1% случаев, но когда пригождаются ты понимаешь, что стоят они тут не зря.

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

                        class StorableShape : Shape { // наследуемся от фигуры
                             private DBObject _store; // агрегируем совсем другой объект
                             alias _store this; // становимся подтипом этого другого объекта
                             this() {
                                  _store = new DBObject;
                             }
                             ...
                        }
                        

                        Целая одна дополнительная строчка кода :-)
                        • +2
                          Интересно, как вы без помощи номинативной типизации сможете отличить человека от, например, обезьяны или андроида.
                          А зачем вам отличать обезьяну от андроида? Поймите вы: если у вас возникла проблема «как отличить обезьяну от человека», значит вы что-то сделали не так. Если в вашем мире нет никаких действий, которые может исполнять андроид, но не может исполнять обезьяна, то вам их и отличать не нужно, вот и всё. Если есть — то они автоматически станут отличимы и будут отличаться там и тогда, где это нужно.

                          Теперь о следующей:
                          1. мы подключаем две библиотеки из ортогональных областей
                          2. мы создаём свой кдласс, объекты которого хотелось бы использовать и с одной и с другой библиотекой
                          3. первая библиотека в интерфейсе указала, что этот объект должен реализовывать метод getId(), который должен возвращать строку удовлетворяющую некоторым ограничениям: он должен состоять исключительно из цифробуквенных символов
                          4. вторая библиотека в интерфейсе указала, что этот объект должен реализовывать метод getId(), который должен возвращать валидный идентификатор в БД вида "#\d+:\d+"
                          5. первый идентификатор можно получить из второго не сложными преобразованиями над строками (base32 например)
                          6. как в одном объекте реализовать оба варианта getId, чтобы один и тот же объект можно было использовать с обеими библиотеками?
                          О ужас! О качмар! О боги, это ж никак нельзя разрулить. А, кстати, как вы разрулите эту проблему в Java? Если у вас первая библиотека будет требовать, чтобы класс реализовывал интерфейс AlphaNumerable, а вторая DbNumerable — оба с методами GetId? И кто помешает это же решение применить в Go?

                          И в любом случае если интерфейс накладывает ограничения на то, какой Id он хочет получить, но не указывает этого в названии метода, то это — плохой интерфейс, как вам уже divan0 отписал.
                          • 0
                            Затем, что с человеком человеку общаться будет интересно, а с андроидом — нет. У обоих есть метод «поддержать беседу». Вы же предлагаете ввести метод «поддержать беседу не как с роботом». Причём для этого вам придётся изменять стороннюю библиотеку, чтобы она вызывала именно его, а не просто «поддержать беседу». Вы глубоко ошибаетесь полагая, что «правильное именование методов» каким-либо образом спасёт вас от данной проблемы. Разве что сделает её появление менее вероятным.

                            В Java эта проблема не разруливается, поэтому там принято давать многословные имена, во избежание возможных конфликтов :-) Вот в C#, например, в случае неоднозначности компилятор настаивает на явном указании к какому интерфейсу относится реализация. Рекомендую почитать эту статью: www.javaportal.ru/java/articles/mnj.html
                            • 0
                              Вы глубоко ошибаетесь полагая, что «правильное именование методов» каким-либо образом спасёт вас от данной проблемы.
                              А почему нет?

                              В Java эта проблема не разруливается
                              А тогда что мы вообще тут сейчас обсуждаем? Если эта проблема не мешает создавать «ынтырпрайзных» монстров на миллионы строк на Java, то почему она помешает их создавать на Go? А если не создавать программы при которых ограничение на 2GiB кода в одном бинарнике становится проблемой, то и вообще проблем не будет.

                              поэтому там принято давать многословные имена, во избежание возможных конфликтов :-)
                              То есть всё-таки проблема разруливается?

                              Вы уж определитесь: у вас есть реальная проблема, которая нам страшно мешает жить (тогда Java будет, по вашему же признанию, контрпримером), либо у вас есть высосанная из пальца проблема, которая, может быть, себя проявит (а может и не проявит), когда вы будете создавать системы на триллионы строк кода миллионами программистов. В этом случае готов согласится — возможно «в далёкой галактике» с триллионами жителей и миллиардами программистов Go и не приживётся. Но мы-то пока на Земле живём!
                              • 0
                                Потому что невозможно синхронизовать работу тысяч программистов, чтобы они не использовали одни и те же имена для разных сущностей в своих библиотеках.

                                Миллионы строк можно на чём угодно написать. Вон, на том же С++ написано куда больше строк. Значит ли это что все проблемы, на которые так любят пинать любители Go, настолько несущественны, что ими стоит пренебречь, Go нужно закопать и всем взяться за изучение свежего сборника граблей С++15?

                                А бы предпочёл пользоваться инструментом который
                                • 0
                                  … который гарантирует отсутствие неожиданностей, а не оставляет им место в тёмном углу, куда мало кто заглядывает.
                                  • 0
                                    Не будет вам никаких неожиданностей. Если у вас возникнет коллизия имён, то ваша программа просто не скомпилируется. А все остальные проблемы, как мы уже обсуждали — это просто неумение работать с Go.
                                • 0
                                  Значит ли это что все проблемы, на которые так любят пинать любители Go, настолько несущественны, что ими стоит пренебречь, Go нужно закопать и всем взяться за изучение свежего сборника граблей С++15?
                                  Во-первых C++15 в природе нет и не будет. Вы уж определитесь — вы про C++14 или C++17 говорите. А во-вторых самая большая проблема C++ (как уже обсуждалось), которую призван решить Go — это его сложность. О чём, в частности, написано в обсуждаемой нами статье (или вы уже забыли). Единственная способ борьбы с ней — это умение говорить «нет». Соответственно любые проблемы в Go обсуждаются на в разрезе «если долго-долго высасывать проблему из пальца, то можно насосать вот это», а «мы X раз сталкивались с проблемой Y, решали её так-то и так-то, а теперь предлагаем всё-таки немного усложить язык, чтобы с ней больше не пришлось сталкиваться».

                                  Пока у вас не будет реальной статистики никто вышей «проблемой», случающейся когда солнечное затмение приходится на пятницу, 13е, заниматься не будет, уж извините. Потому что всё обсуждение в итоге свелось вот к этому:
                                  Потому что невозможно синхронизовать работу тысяч программистов, чтобы они не использовали одни и те же имена для разных сущностей в своих библиотеках.
                                  Почему нельзя? У других как-то получаются. Да, бывают коллизии, ну так нужно связаться с разработчиками, обсудить их, разрулить, в крайнем случае. А в большинстве случаев, если у людей мозги есть, проблем не будет. Как-то же люди на C пишут, а там вообще плоское пространство глобальных имён. Так можно дойти до того, чтобы вообще запретить людям имена придумывать, а заставлять их использовать рандомно сгененированные последовательности букв и цифр. А то как бы чего не вышло. Единственная реальна проблема, которую несёт в себе подход Go — это возможные коллизии имён. Тут вы правильно заметили. Да, это [потенциальная] проблема, но на практике коллизии случаются достаточно редко для того, чтобы вокруг этой проблемы не нужно было выстраивать огород — вот и всё.
                        • +1
                          И всё же, читая «вы создаете класс», понятно, что вы пытаетесь создать уже знакомые вам ситуации там, где они не создаются просто потому, что задачи решаются иначе. В Go очень простая и понятная модель — структуры (классы, по вашему) — описывают свойства, а интерфейсы — описывают поведение. Это очень мощная модель, если ей не сопротивляться.

                          Ваши примеры вроде методов интерфейса iAmHuman() — нонсенс в Go, потому что интерфейс не определяет свойство, человек ты или обезъяна. Примите же это, наконец :)
                          • 0
                            Вы сейчас с JS программистом разговариваете. О каких «знакомых мне классах» вы говорите? ;-) Реализация классов через структуры+UFCS мне импонирует куда больше чем через конструкторы+прототипы. Там не менее не стоит закрывать на проблемы глаза и притворяться будто их нет. Развитие языков происходит только тогда, когда проблемы существующих осознаются и начинает искаться их решение. Правда зачастую одни проблемы меняются на другие.

                            Интерфейсы в Go просто группируют сигнатуры методов, чтобы уменьшить объём копипасты при объявлении функций. А вот поведение описывается функциями. При этом вы вынуждены публичным методам объектов давать глобально уникальные имена. Причём глобальные не в рамках класса, не в рамках пакета, не в рамках приложения, а в рамках всей экосистемы Go.

                            Это не мой пример, а вариант костыля. Другой вариант (ни чем принципиально не отличающийся впрочем) вы же приводите ниже.
                            • 0
                              Давайте вы сначала пройдете Tour of Go, а потом будете пытаться «поломать язык»? :)

                              Вообще, специально для людей вот с таким подходом, в разных докладах по Go говорилась фраза «Don't fight the language». Решайте практические задачи способами языка, а не приносите старые паттерны и жалуйтесь, что они не так работают, как вы ожидаете.
                              Поверьте, Go писали далеко не дураки, и вот так, сгоряча, даже не почитав основ, пытаться «раскусить» и найти там глупость у вас не выйдет.
                              Ну, тоесть, себя вы может и сможете в этом убедить, но это же не интересно :)
                              • –1
                                Ну ок, поверю вам на слово, что Go — идеальный язык. Все остальные ведь дураки, не прошедшие Tour of Go, раз заменили коды возвратов исключениями, одиночный диспатчинг множественным, а динамическую типизацию статической :-)
                                • +1
                                  Ну ок, поверю вам на слово, что Go — идеальный язык.

                                  Или покажите, где я такое сказал или забирайте свое обвинение обратно )
                            • +1
                              Вы сейчас с JS программистом разговариваете. О каких «знакомых мне классах» вы говорите? ;-)
                              О всё тех же самых. Ваши мозги также безнадёжно искалечены Smalltalk'ом, как и мозги Java-разработчиков и C++-разработчиков.

                              При этом вы вынуждены публичным методам объектов давать глобально уникальные имена.
                              И вот тут это прекрасно видно. То, что у разработчиков Smalltalk'а случилось помутнение сознания и они засунули методы в объекты не означает, что так и должно быть. Подавляющее большинство методов объектам не принадлежат! Метод «изобрази себя» логически не принадлежит на объекту «круг», ни объекту «квадрат», ни объекту «экран», ни объекту «принтер» — он связывает между собой эти сущности. И именно за счёт этого, как правило, между разными методами не возникает коллизий даже с короткими именами.

                              В тех же редких случаях, когда коллизии возможны (как в примере с MarshalJSON/MarshalXML) у вас возникает связь не с двумя входящими объектами, а с входящим объектов и выходящим, а так как сигнатура выходящего в типе не участвует то возникает естественное желание внести её в имя метода). Поймите же вы, наконец, что эти методы названы так, как они названы не для того, чтобы «развести» потенциальные коллиззии, а чтобы облегчить чтение программы. Если у вас есть интерфейс, который включает в себя метод getId, который должен генерировать алфивитно-цифровой Id, то его название плохо не из-за того, что могут коллизии возникнуть, а из-за того, что такое название подразумевает, что вам годится любая строка в качестве Id, что есть неправда.

                              Если вы хотите писать на Go — то нужно писать на Go, а не пытаться придумать как выразить программу на C++, Java, или JavaScript на Go. Программу на C++ лучше всего писать на C++, а программу на JavaScript — таки на JavaScript, как это ни удивительно.
                              • 0
                                Хамство не сделает ваши аргументы более весомыми :-)

                                Коллизии редки лишь пока кода мало и тот достаточно централизован. С ростом числа библиотек, которые пишут люди разной квалификации параллельно, вероятность конфликтов будет только расти. И никакие административные методы не смогут справиться с этим стихийным бедствием. Именно для этого номинативная типизация и была придумана — ты явным образом указываешь какой интерфейс собираешься поддерживать, а в своём интерфейсе волен использовать любые сигнатуры без риска напороться на конфликт с чужим интерфейсом. То, что в Java она реализована криво не означает, что нужно впадать в противоположную крайность.

                                Если у вас есть интерфейс, который включает в себя метод getId, который должен генерировать алфивитно-цифровой Id, то его название плохо не из-за того, что могут коллизии возникнуть, а из-за того, что такое название подразумевает, что вам годится любая строка в качестве Id, что есть неправда.
                                Как же его правильно назвать? GetIdWithAlphaNumericalLettersAndUnderscoreOnly? :-)
                                • 0
                                  видимо, .getUUID. Если уж мы приперлись решать проблему — давайте начнем с низов. Такие гадости, как GetIdWithAlphaNumericalLettersAndUnderscoreOnly — это мутанты, возникающие в силу того, что кто-то в свое время решил «а давайте-ка мы забабахаем свой стандарт идентификаторов!». И в результате n+1 стандарт идентификаторов. В проекте.
                                  • –1
                                    В одной библиотеке используется один стандарт, в другой — другой стандарт. Друг о друге они вообще не знают. Да и не должны. А нам надо работать и с ужом и с ежом — типична ситуация. UUID — это вообще другая опера, которая далеко не всегда годится.
                    • +3
                      На самом деле интересный пример, спасибо. В реальном мире, конечно, подобный код очень вряд ли встретиться, ещё и с «ой, где-то ошиблись», отсутствием доступа к оригинальным исходникам, описывающим интерфейсы и невозможностью адекватно переименовать.

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

                      Поэтому давайте объективно — если уж и произошел такой костыль, который нельзя исправить, то нужно искать workaround, если всё же хочется обезопасить себя на этапе компиляции. И workaround тут я вижу следующий — вместо Human использовать другой интерфейс-wrapper, отличный от Human. Вот как-то так (embedding наше все):
                      type MyHuman interface {
                      	Human
                      	MyRun()
                      }
                      
                      func (c *Person) MyRun() {
                      	c.Run()
                      }
                      
                      // ну и соответственно в playInFunnyGames использовать не Human, а MyHuman
                      func playInFunnyGames(h1 MyHuman, h2 MyHuman) {
                      	h1.Run()
                      	h2.Run()
                      	fmt.Println("Innocent game complete")
                      }
                      

                      play.golang.org/p/EZrgR74HGd
                      • +1
                        Обойти эту проблему можно. Выкрутится по месту разными способами. Вопрос: стоит ли ради её решения усложнять язык? И тот факт, что ни Java, ни C++ этого не стали делать однозначно говорит нам о том, что проблема эта — скорее теоретическая и усложненять такой такой простой язык как Go ради неё явно не стоит.
                        • 0
                          Ну про «ломать язык» точно речь не идет. Но да, как же сложно class-based ООП-mindset выпрямлять )
                      • 0
                        Я продолжу этот пример:

                        // для человеков просто создаём песочницу и пусть бегают в ней
                        func RunInSandbox(h Human) {
                            var sb = &SandBox{}
                            sb.Put( h )
                            h.Run()
                        }
                        
                        // для процессов временно создаём изолированный контекст, который не влияет на текущий
                        func RunInSandbox(h Process) {
                            IsolateContext()
                            defer DestroyContext()
                            h.Run()
                        }
                        
                        // создаём уничтожитель миров
                        w := &WorldDestroyer{Name: "Lucky"}
                        
                        // какая реализация будет вызвана?
                        runInSandbox( w )
                        

                        • +3
                          какая реализация будет вызвана?

                          Будет вызывана реализация «RunInSandbox redeclared».
                          В Go нет оверлоада функций.

                          Вы продолжаете мыслить классами и другими языками, но записывать это на Go и изобретать код, который не только далек от практической реальности, но и невалидный вообще.
                          • –2
                            То есть подключив обе библиотеки я получу ошибку компиляции? Славно :-)
                            • +4
                              Вы хотите выучить язык по моим ответам на ваши придуманные проблемы? )

                              Ошибку не получите, потому что в случае двух разных пакаджей это будут две разных функции: packageA.RunInSandbox() и packageB.RunInSandbox().
                              • 0
                                Вы пока не убедили, что его стоит изучать ;-)

                                Ок, а что будет с вызовом w.RunInSandbox() или такой вызов разрешён только для функций параметризированных конкретной структурой?
                                • +2
                                  Именно. Познакомьтесь с системой типов в Go, станет понятно, и всё это гадание на кофейной гуще станет не нужным.
                                  В Go, как уже выше писалось, типы различаются на «интерфейсный тип» и «конкретный тип», и любой конкретный тип может неявно удовлетворять «интерфейсный тип». Там используется смешанный подход early/late binding, concrete types чекаются компилятором, интерфейсные типы чекаются рантаймом.

                                  А ресивер метода да, может быть определен только на «конкретный тип». В спеке это четко описано.
                                  • 0
                                    Очень жаль, полноценный UFCS — полезная штука.
                                    • –2
                                      Вам жаль, что фишки из совершенно другого языка, надобность которого в Go реализуется другим путем, нету в Go?

                                      image
                                      • +2
                                        Мне жаль, что удобные паттерны не реализуются по причине «у нас тут всё по своему, мы же не дураки» :-)

                                        Да и то, что исключения заменили ручной раскруткой стека, очень красноречиво показывает насколько они «не дураки».

                                        Открываем первый попавшийся файл: github.com/golang/go/blob/master/src/compress/gzip/gzip.go

                                        На почти 300 строк 12 конструкций вида:
                                        if err != nil {
                                        	return err
                                        }
                                        


                                        То есть, более 10% всего кода потрачено на эту тупую копипасту. Примечательно, что ничего другого, кроме как «прокинуть ошибку наверх», тут не делается. И чтобы не заниматься этой ерундой во всех современных языках есть исключения.
                                        • 0
                                          Вот не надо про исключения. Вы чистой воды теоретик, это ясно.

                                          У меня были недавно споры по поводу исключений. Человек вот точно также доказывал, что исключение — единственный true метод обработки ошибок, и все обвинения в том, что исключения часто неправильно используются — это потому что «программисты плохие», язык тут не причём. В теории все звучало круто, пока не дошло до практики.
                                          Код этого человека, с обработкой ошибок на исключениях, получил шанс пройти испытание практикой — нужно было изолировать бинарник в докер, и, по ходу, ошибок, выяснять, чего не хватает, порты, связи, файлы — вот это всё. И все эти аргументы разбились в пух и прах — практически ни одна из ошибок не была правильно отрепорчена, метод «выбросить исключение и забить» показал себя на ура — единственным спасение оказался strace. Вопрос — зачем тогда вообще обрабатывать ошибки исключениями, если даже человек, яростно их защищающий и «умеющий правильно их использовать» не может хорошо этим инструментом пользоваться.

                                          Всё, простите, но предлагаю закончить этот спор теоретиков и практиков.
                                          Хотите модных фишечек PLT — есть масса других языков. Go оставьте для продуктивности и практического применения. Точка.
                                          • +1
                                            Да-да, ни одной строчки за 20 лет практики так и не написал :-)

                                            Нет уж, сказали А — говорите и Б. Код приложений с их репортами в студию!
                  • 0

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