Открыть список
Как стать автором
Обновить

Комментарии 320

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

Так и есть, а ещё все всегда корректно обрабатывают nil ресивер в методах, а зубные феи существуют

Не вижу противоречий с автором вашем комментарии
+1
Уже мало кто помнит, что был такой язык… а Модулу и подавно ((
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Т.е. для микросервисов вполне применимо

Пока вы будете писать


res, err := makeSomething()
if(err != nil) {
    ....
}
...

Проекты на спрингбуте или кваркусе уже будут в продакшене :)))

… будут в продакшене рестартовать от NPE и OOM (потому что JVM на пару со спрингом съели всю память задолго до выполнения хоть какой-то бизнес логики)

Только не под GraalVM

Спринг — да, только не под GraalVM :D
Случаи успешной сборки приложений, использующих хотя бы рефлексию, единичны. И скорее относятся к категории взаимной аннигиляции ошибок.

Вам выше quarkus предлагали, там и graalvm и memory footprint и время старта отличные)

Отлично. Осталось все переписать со спринга на кваркус и потом пытаться как-то это собрать на GraalVM. Не проще ли тогда на Go?

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

Так на Go скорость разработки выше :)

НЛО прилетело и опубликовало эту надпись здесь
Не знаю, качественный и безопасный код на Го писать одновременно дольше и сложнее. А в процессе ещё и жутко противно, потому что от мысли «а из какой криокамеры достали тех злодеев, которые выкатили ЭТО на-гора во второй половине нулевых» избавиться совершенно невозможно.

Что значит из какой криокамеры?
Это же Томпсон и Пайк из белл-лабс, авторы юникса и си.
Пайк ещё и соавтор систем план-9 (названной в честь трешового фильма ужасов) и инферно (названной понятно в честь какой локации).
Так что он вполне мог кричать АХАХАХ, оживляя гошечку.

Вы можете обосновать свое утверждение? Оно несколько не очевидно. Особенно что касается concurrency — реализация в Go позволяет писать намного более качественный и безопасный код

После STM в других языках гошные каналы — это таки шаг назад.

STM это куда, извините?

Это software transactional memory.

После вот этой статьи утверждения о простоте concurrency в Go воспринимаются с большим подозрением…

НЛО прилетело и опубликовало эту надпись здесь
Вообще-то ещё у Янга в книге «Алгоритмические языки реального времени. Конструирование и разработка» (русский перевод выпущен в 1985 году) разбирается модель рандеву, реализованная в Ada-80, и делается вывод о её небезопасности. Возможно, конечно, что в последующих стандартах что-то поменялось…

Рандеву в Ada построено на черновике CSP Хоара — с безымянными каналами и именованными получателями, тогда как в основе Go финальная редакция книги — с именованными каналами и безымянными получателями.
А есть еще Erlang, по сравнению с которым гошная concurrency — детский сад.

Вот по сравнению с Erlang с единственным входным каналом на процесс таки пусть будет лучше Go. Go ещё и компилируемый с доступной статической типизацией. Штатно добавить обёртку "послать сообщение о панике" и можно будет строить схему супервизоров, как в OTP. Что ещё нужно?

НЛО прилетело и опубликовало эту надпись здесь

Горячее обновление — да, согласен, это одна из хотелок. Это то, что в Go не предвидится — они идут путём наоборот. С другой стороны, индустрия наработала множество методов обойтись без этого горячего обновления через разделение хранилища состояния и кода обработки, и контейнеризации каждого; если 99% загоняют код в образы докера/кубера/etc., их горячее обновление на месте не волнует. Его ещё и совместить с нынешними devop-средствами, мягко говоря, непросто.


Отказоустойчивость — как уже сказал, обёртка типа "свистнуть супервизору про панику" плюс простой супервизор (пара сотен строчек) и получим то же самое в Go. Если никто не делает — наверно, в таком виде оно не нужно?


"Простое и эффективное построение распределенных систем" — если речь про кластеризацию Erlang, то она только сейчас становится похожей на что-то более-менее приличное, и то со средствами не из коробки. В каком там релизе начали понимать, что нодовый heartbeat надо отделить от межнодового обмена данными? И почему global рвётся от любой проблемы кластера?


"Мягкого реального времени" — да они, считай, все такие сейчас. И Go тут улучшают с каждым релизом.


"простой парсинг протоколов" — о чём речь? О binary matching? Это очень вспомогательная фича, которая неспособна решить, например, JSON (строки в виде списков в 2021 уже не смешны, а страшны… а с бинаркой слишком многое неудобно).
Ещё что-то?

НЛО прилетело и опубликовало эту надпись здесь
Докеры с куберами — костыли и ненужная прослойка, когда все, что нужно для комфортной работы уже есть в языке.

И в язык входит компиляция, разлив файлов по системам, корректировка конфигов, раздача опций, синхронизация рестартов и т.п.? Тогда это уже не Erlang/OTP, а какие-то супернавески над ним (равномощные куберу), я таких не видел. Назовите.


Я писал на Go 2 года и устал материться и плеваться с этого убожества. Сейчас пишу на Erlang, Standard ML и F* и мне ништяк.

Ну а я писал на Erlang 5 лет. На Go не пишу, косо посматриваю, но случая пока не было, всё по чужим отзывам (которых тысячи). По Erlang отзыв я дал — чем дальше, тем больше его отсталость начинает мешать. Если Go убожество (не спорю), то Erlang такое же вдвое.


Если хотели поспорить, то не с тем начали — мне плевать на чужое мнение.

На хабре меня интересует дискуссия ради интересного обсуждения с новыми фактами, неожиданными аргументами, и т.д. Чистая победа без остального мне не нужна, для самоутверждения есть масса других мест :)

НЛО прилетело и опубликовало эту надпись здесь

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

Скорость разработки выше для относительно простых задач, а вот с ростом сложности проекта уже начинают мешать ограничения языка и скорость разработки падает, зачастую вместе с читаемостью кода. Например, пресловутое отсутствие дженериков затрудняет написание обобщенного кода. А невозможность перегрузки операторов вынуждает, например, вместо «z := x + y» писать плохо читаемый ужас вроде z.Add(&x, &y). Но радует, что язык развивается и постепенно избавляется от шероховатостей.

Скорость разработки выше для относительно простых задач

что в вашем понимании относительно простая задача?

Задача, простая относительно более сложных задач %)

К сожалению в плане библиотек и их качества го пока еще в догоняющих

Как будто приложения на Go не падают от null nil pointer dereference

Но это ведь nil, а не null, значит совершенно другое!

if err != nil { } писать долго, вот на java писать быстро

Как-то очень странно слышать такую критику в контексте java, где без автоматической генерации сотен конструкций (throws — у методов, конструкторов, get/set-методов, лямбд и многого-многого другого) жизни вообще нет? На go можно писать вообще без IDE, и если делать это не двумя пальцами, if err != nil {} вообще не замечаются.


Почему условный jetbrains с java, в котором на каждое второе нажатие на клавиатуру генерируется какой-то код — это хорошо, а условный jetbrains с go — это плохо?

На go можно писать вообще без IDE

А зачем?


где без автоматической генерации сотен конструкций

У меня прикручен ломбок и объекты выглядят чисто и просто.


а условный jetbrains с go — это плохо?

Для меня Го не плохо, а шаг назад. Я должен писать тонны однотипного кода, вместо сосредоточения на бизнес логике.

Для меня Го не плохо, а шаг назад. Я должен писать тонны однотипного кода, вместо сосредоточения на бизнес логике.

А для меня — шаг вперед. Скорость написание кода для меня упирается в скорость мыслительного процесса (о той самой бизнес-логике), а не в скорости набивания на клавиатуре однотипных конструкций и шаблонов, как я это делал в java.


У меня прикручен ломбок и объекты выглядят чисто и просто.

А у меня код на go выглядит чисто и просто так просто потому, что он на go. И тот же goland умеет автоматически скрывать однотипные if err != nil { return nil, err } (точно так же, как idea скрывает сотни других однотипных конструкций в java). Что дальше?

Скорость написание кода для меня упирается в скорость мыслительного процесса

вот смотрите пример чистой логики на джава, которая читает сообщение из кафки и потом в нее же записывает.


    @Incoming("fruit-in")
    @Outgoing("fruit-out")
    fun makeSomething(item: Fruit): Fruit {
        ...clean logic
    }

можно теперь пример кода на Го, который затмит сие творение?


А у меня код на go выглядит чисто и просто так просто потому, что он на go.

опять же можно пример такого чистого кода?


вот пример на джаве когда есть список урлов и надо пройтись по всем им, получить данные с них, но не более 5 одновременных вызовов и потом все сгруппировать


Flux.just(url1, url2, ...)
    .flatMap(url -> getContent(url).onErrorReturn("default value"), 5)
    .reduce("", (accum, content) -> accum + content)

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


скрывает сотни других однотипных конструкций в java

чесно, я вообще не понимаю какие конструкции вы имеете ввиду.

можно теперь пример кода на Го, который затмит сие творение?

Конечно, абсолютно так же скрыв весь support-code, я напишу так:


func (fr *fruitConsumer) OnMessage(item Fruit) error { ... }


.onErrorReturn("default value")

Отличный пример, почему обработка ошибок в go вынуждает писать нормальный код. А если сеть на сервере совсем упала? Вместо результата будет тыква. Жесть какая-то.

func (fr *fruitReader) OnMessage(item Fruit) error { ... }

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


Отличный пример, почему обработка ошибок в go вынуждает писать нормальный код. А если сеть на сервере совсем упала? Вместо результата будет тыква.

Это как пример был, что на любую ошибку мы забиваем и вернем значение по умолчанию, если надо обрабатывать конкретные ошибки или сделать предикат для обработки всеех ошибок то это не проблема.


.onErrorReturn(ConnectTimeoutException.class, "default value")

так где же ваш чистый код? и сотни скрывающихся конструкций в Джаве?

а я всю обвязку с кафкой

Нет. Это не вся обвязка. Incoming и Outgoing откуда-то дожны взяться. Формат сериализации тоже нигде не указан. Вот выложите минимальный пример проекта, читающий/пишущий фрукты — я напишу аналогичное на go. Да, библиотека для go более низкоуровневая и содержит меньше сахара, но нужно учитывать, что обвязки один раз под проект пишутся и много раз используются.


.onErrorReturn(ConnectTimeoutException.class, "default value")

Это как пример был

Это все равно жесть. Это конечно "только пример", но это показательный пример, почему так делать не нужно. Я видел нормальное такое количество подобных библиотек на go (если прям очень нужно), и все они своей неудобностью подчеркивают, почему так делать не нужно. Да, без них это будет страница кода, но это будет страница кода, безупречно отрабатывающая во всех ситуациях без неопределенного поведения и тихого заглатывания ошибок.

Формат сериализации тоже нигде не указан

вот сериализатор


import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer;

public class FruitDeserializer extends ObjectMapperDeserializer<Fruit> {
    public FruitDeserializer(){
        super(Fruit.class);
    }
}

Incoming и Outgoing откуда-то дожны взяться.

# Configure the Kafka source (we read from it)
mp.messaging.incoming.fruit-in.connector=smallrye-kafka
mp.messaging.incoming.fruit-in.topic=fruit-in
mp.messaging.incoming.fruit-in.value.deserializer=com.acme.fruit.jackson.FruitDeserializer

# Configure the Kafka sink (we write to it)
mp.messaging.outgoing.fruit-out.connector=smallrye-kafka
mp.messaging.outgoing.fruit-out.topic=fruit-out
mp.messaging.outgoing.fruit-out.value.serializer=io.quarkus.kafka.client.serialization.ObjectMapperSerializer

вот и все готовое приложение на кваркусе.


теперь ваша очередь.


Да, без них это будет страница кода, но это будет страница кода, безупречно отрабатывающая во всех ситуациях без неопределенного поведения и тихого заглатывания ошибок

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

mp.messaging.outgoing.fruit-out.connector=smallrye-kafka
mp.messaging.outgoing.fruit-out.topic=fruit-out
mp.messaging.outgoing.fruit-out.value.serializer=io.quarkus.kafka.client.serialization.ObjectMapperSerializer

Жесть. go так не может, считайте это своей победой, если хотите, лол.

Что го не может? Использовать properties-файлы? Да вроде прекрасно может.

ну да, лучше на Го потратить неделю и написать Войну и Мир для этой простой задачи :)

Лучше написать немного явного кода, чем утонуть в мегабайтах неявного, когда нужно на отойти на шаг от стандартного юзкейза.

Куда большая проблема — то, что эта вся “простота” пожирает память и ядра процессора, как не в себя.

Из-за того, что мегабайт кода вы не написали руками, а он нагенерировался из написанных вами двух строчек… процессору ведь не легче. Ему всё равно вот всё это ведь нужно исполнять.

В результате получается, что Go — это о простых программах (ну или сложных, как Kubernetes — но тогда они и выглядят сложными внутри), а Java — это безумных глюкомонстрах, которые кажутся простыми… если не всматриваться.

Каждому — своё, конечно.

Да, все абсолютно так же, как с тем примером, который кидали выше. Вроде все очень просто, но когда начинаешь отвечать на сложные вопросы, вроде:


  • А каждый такой декоратор создает новый тред?
  • А что весь этот код делает, если не может подключиться к кафке?
  • А что этот код сделает, если подключение к кафке умрет посериде исполнения? Упадет?
  • А как что-либо из этого изменить?
  • И многие-многие другие

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

Вроде все очень просто, но когда начинаешь отвечать на сложные вопросы, вроде

В доке есть ответы на эти вопросы.
так же никто не запрещает взять низкоуровневую кафку и работать с ней. Те джава тем и хороша что вы можете работать в разных парадигмах и выбирать уровень абстракции.


На деле при эксплуатации возникнет просто нереальное количество проблем, которые хорошо, если заметят.

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

Куда большая проблема — то, что эта вся “простота” пожирает память и ядра процессора, как не в себя.

Предположим, что ваше утверждение верно и наше джава приложение жрет память — вот цена в Орегоне на EC2 инстансы:
t3.nano 0,5 ГиБ 0,0052 USD за час
t3.micro 1 ГиБ 0,0104 USD за час


получаем разницу в 40 долларов в год, это меньше чем час работы среднего джависта в штатах!!!

Некоторое время назад статья от яндекса была, по упаковке python приложений, и они хотели от докера перейти к упаковке в бинарники или что-то такое. Вобщем причина «когда приложение запущено на 10к серверов, то эти пара процентов выливаются в приичную сумму», а тут приезжают джависты на белом коне «Гуляем, я плачу»=)
Вобщем вопрос, платить то кто будет за 10к инстансев, программист который решил «Да чем там той памяти, она ж дешевая»? или Джава годится только для приложений которые в одном эксзщемпляре запускаются на 1гб памяти? спору нет, чеб тогда не транжирить ресурсы=)
  1. Те разницу по времени в разработке вы успешно пропустили и расходы посчитали только на память???


  2. слышали You Are Not Google или во вашему весь мир запускает 10К серверов???


  3. Если мне надо микросервис на 10К инстансов и считать каждый мегабайт и цикл цпу то я возьму Раст.


Кажется, дискуссия из обсуждения проблем языка переросла в обсуждение наличия библиотек и фреймворков для конкретных языков. Есть тот же go-kit далёкий от идеала, но имеет некоторое количество реализованных транспортов, и спрятать реализацию за интерфейсом в go вполне себе можно. Хотя это не отменяет основных проблем go: многословность и невыразительность. В том же go-kit в некоторых местах используется interface{}, считай, void*, что на корню убивает все прелести статической типизации, а иначе сделать не выйдет, потому что нет обобщений. Отсюда и проблемы с библиотеками, которые по определению нельзя реализовать по типу stl, и это печалит. Либо генерировать код, как предлагает Пайк, что странно в 2021, когда все другие языки давно умеют в дженерики, либо — копипастить одни и те же алгоритмы, подставляя свои значения, либо использовать интерфейсы и ломать типизацию, используя на входе и выходе interface{}, и это ужасно. Вот нашелся в интернете человек, скажем, который реализовал нужный алгоритм и выложил в виде пакета. Все классно, но у него, к примеру, используется int32, а мне нужен int64, все, баста карапузики, только копипаста. Некоторые, конечно, заморачиваются и пилят генераторы кода с инструкциями по использованию, но это же очевидный костыль. Есть ещё масса других проблем поменьше, о которых уже выше сказали, типа тех же ошибок, присваивания, nil и приведения типов. Прошу прощения за объем — наболело за два года использования go. Как-то пришлось делать свою реализацию каналов amqp, потому что общедоступная никак не реагирует на разрыв соединения и такого навалом.

где без автоматической генерации сотен конструкций (throws — у методов,

Это цивилизованный аналог if err != nil {} для явного проброса исключений. При сравнении с другими языками можно холиварить насколько это нужно, но при сравнении с го странно ставить это в минус


конструкторов

Они необязательные, автогнереить их ненадо если не используешь


get/set-методов,

Сейчач в джаве есть рекорды(если говорим про современную джаву). Или мы сравниваем старую джаву с новой версией го?


лямбд

Вот тут непонял, а где там автогенреция иде нужна?


Я могу назвать много мест где джаве есть куда расти, но несогласен с вашим списком.

Это цивилизованный аналог if err != nil {} для явного проброса исключений. При сравнении с другими языками можно холиварить насколько это нужно, но при сравнении с го странно ставить это в минус

Я с этим не согласен и считаю, что система в go удобнее и более гибкая. Особенно с тех пор, как error wrapping завезли в стандартную библиотеку. В go практически любая функция может вернуть ошибку и это 1) соответствует действительности 2) позволяет добиться чрезвычайной гибкости. Аналогичный код на java писать практически невозможно: либо throws Exception, либо throws over9000 different exceptions во всей цепочке методов.


Я могу назвать много мест где джаве есть куда расти, но несогласен с вашим списком.

Это не список "то, что мне не нравится в java", а список того, где "на java с трудом можно писать без автоматического подставления сниппетов со стороны IDE, ибо слишком многословно", исключительно в ответ на критику, называющую go многословным относительно java.


Начал кстати смотреть, что там завезли с records, нашел статью: "Одной из проблем Java является ее многословность и объем необходимого стандартного кода. Это общеизвестно.". Ну т.е. даже люди, пишушие на java это особо не отрицают.


Они необязательные, автогнереить их ненадо если не используешь

Не использовать конструкторы в java — это как?

В go практически любая функция может вернуть ошибку

Начнем с того что ошибка в го ничем не отличается от значения. И их очень легко пропустить и пойти дальше с ошибочным значением.
res, err := makeSomething() мы забили на ошибку и пошли дальше с результатом res — ни в джаве, ни в расте такого уродства вы не получите


Аналогичный код на java писать практически невозможно:

на джаве вы можете писать


  • используя checked exceptions
  • unchecked exceptions
  • взять vavr и гонять везде Either
  • сделать такую же фигню как и в Го и возвращать Pair

и не забываем что в Го вы так же можете получить Panic

И их очень легко пропустить и пойти дальше с ошибочным значением.

Нет, не просто. Есть линтеры errcheck и ineffassign (которые, кстати, полезны во всех ситуациях, не только в ошибочных). В продакшон-коде такие ошибки не допускаются.


и не забываем что в Го вы так же можете получить Panic

И?

Нет, не просто

Да ну блин, я вам говорю про концепцию языка, который спокойно позволяет делать такие вещи. А вы считаете что это нормально. Я не знаю такого маразма ни в одном другом языке.


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

Тогда концепция java "плохая", потому что позволяет делать пустые обработчики exception.


то что в нутрях го это нормальный подход — паник/рекавери и это ни что иное как джава ексепшены.

И? Оно не используется как exception в java. RTFM

Тогда концепция java "плохая", потому что позволяет делать пустые обработчики exception.

Не, это не так работает. Пустой обработчик это тоже обработчик. просто вы решили что вам эта ошибка не нужна. Это вполне рабочая ситуация в любом языке, в Го же вы получаете ошибку и результат одновременно
В Расте вы получили Result и с него вы можете достать либо ошибку либо хорошее значение, в джаве вы получаете или хороший результат либо ексепшен, а в го вы получаете всегда результат, есть там ошибка, нет ее там. и сделать по другому вы не можете.


Оно не используется как exception в java.

серьезно? Почитайте про парсер json имплементацию в Го, там оно во всю использовалось.


или вот наугал взяв https://github.com/golang/go/issues/30489
отличное поведение паниковать когда не можешь разпарсить жсон :))))

или вот наугал взяв https://github.com/golang/go/issues/30489

А слабо было прочитать, что по ссылке написано? Панику вызывает не encoding/json, а клиентский код.


в Го же вы получаете ошибку и результат одновременно

И это в некоторых ситуациях чуть ли не обязательно. Особенно при работе с i/o.


Пустой обработчик это тоже обработчик. просто вы решили что вам эта ошибка не нужна.

И в чем тогда суть претензии? В go можно сделать пустой обработчик, в java можно сделать пустой обработчик.

И это в некоторых ситуациях чуть ли не обязательно. Особенно при работе с i/o.

А можно какой-нибудь (пусть и абстрактный) пример? Где можно осмысленно использовать и результат и ошибку.


И в чем тогда суть претензии? В go можно сделать пустой обработчик, в java можно сделать пустой обработчик.

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

А можно какой-нибудь (пусть и абстрактный) пример? Где можно осмысленно использовать и результат и ошибку.

io.Reader, io.Writer

Больше интересно, что как выглядит логика поверх такого интерфейса. Особенно если ошибка не EOF, а что-то другое.

Например short read/short write, которые часть POSIX-a

Было записано/считано N bytes, дальше ошибка. Приложение должно уметь это обрабатывать, но в этом месте очень часто баги. Не берусь судить, из-за того что разработчики такого не знали, или изначально работали с языком который завернул им все в «либо ошибка либо результат»

И в этом месте очень часто баги

Спасибо, любопытный пример.

Например short read/short write, которые часть POSIX-a
А не расскажите, где и как они возникают?

Потому что всякие read/write именно что возвращают либо результат, либо ошибку.

Не берусь судить, из-за того что разработчики такого не знали, или изначально работали с языком который завернул им все в «либо ошибка либо результат»
А ничего что именно так устроены системные вызовы в Linux? И они в принципе не могут вернуть одновременно и ошибку и что-то ещё?

P.S. И нет, я могу себе представить другой POSIX, в другой, параллельной, вселенной… но вроде как вы говорите про нашу… или нет?

Посмотрел кстати на rust std:io::Read — и что-то не очень ясно, как они обрабатывают эту ситуацию ("что-то пошло не так, но считать n байт успели"). Возвращает ошибку только на следующий вызов read()?

Там же написано в документации:


Errors

If this function encounters any form of I/O or other error, an error variant will be returned. If an error is returned then it must be guaranteed that no bytes were read.
Возвращает ошибку только на следующий вызов read()

Ну т.е. либо так, либо теряет некоторое количество данных.

If an error is returned then it must be guaranteed that no bytes were read.

То есть да, так. Другой вопрос — а зачем знать о появлении ошибки одновременно с появлением очередной порции данных, а не на следующей итерации, учитывая, что read() практически всегда должен вызываться в цикле?

Это имеет смысл и реализация в rust мне нравится больше, io.Reader/io.Writer в go, на самом деле, проблематичны и по ряду многих других причин (особенно, если копать глубоко).

Я думаю не только мне будет интересно узнать про проблемы io.RW подробнее. Расскажите пожалуйста.
то что в нутрях го это нормальный подход — паник/рекавери и это ни что иное как джава ексепшены.

На самом деле похуже, потому что в Go иерархии ошибок делать не принято.

Я с этим не согласен и считаю, что система в go удобнее и более гибкая. Особенно с тех пор, как error wrapping завезли в стандартную библиотеку. В go практически любая функция может вернуть ошибку и это 1) соответствует действительности 2) позволяет добиться чрезвычайной гибкости. Аналогичный код на java писать практически невозможно: либо throws Exception, либо throws over9000 different exceptions во всей цепочке методов.

Так как раз это throw позволяет явно прокидывать эксепшены с минимальным количеством кода. Просто пример-сервис, в нем несколько уровней абстракции, при возникновении ошибки нужно вернуть ошибочный код. В джаве это будет легко сделать, но при этом это явное передача в каждом случае. И на каком-то уровне тебе все-равно придется обработать эту ошибку.


Начал кстати смотреть, что там завезли с records, нашел статью: "Одной из проблем Java является ее многословность и объем необходимого стандартного кода. Это общеизвестно.". Ну т.е. даже люди, пишушие на java это особо не отрицают.

Так я тоже согласен, но в джаве как раз это активно меняется. Из примеров что было сделано за последние пару лет-новый switch, records, instanceOf, var. В работе primitives(хотя тут цель другая), destructuring.
Ваш пример попал как раз на то что уже изменено. Да, куча легаси проектов не будут использовать это, но если мы говорим в целом про язык-в джаве этот уже можно писать без этого бойлерелплейта.
Ну и го тоже довольно многословный, из лаконичных языков пример для меня это python.


Не использовать конструкторы в java — это как?

Типичный код на джаве


public class HelloWorld{

     public void doSmth(){
        System.out.println("Hello World");
     }
}

Может вы что-то другое имели ввиду и я не так понял, но как видите никакого конструктора нету(в коде). Если он ненужен то объявлять его нет необходимости.

Если он не нужен то объявлять его нет необходимости.

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

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

Я возможно непонимаю, но нехотите — неиспользуйте конструкторы совсем, но бойлерплейт и автогенерация констукторов ненужны.
Если есть примеры покажите на другом языке как вы хотите передавать параметры

Я возможно непонимаю, но нехотите — неиспользуйте конструкторы совсем

И что использовать вместо них? Так-то можно и классы совсем не использовать, в теории. На практике — с трудом.

Ну, можно писать
res, _ = makeSomething()
...


А ошибки обрабатывать методом перезапуска упавшей программы. Многие так и делают, и вполне счастливы :-)

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

Не будут, на спрингбуте в это же время будут писать очередной совершенное ненужный AbstractFactoryFactory, XXXService, XXXProvider и прочий говнокод

Пишу на Go больше 2х лет — одно удовольствие. Haters gonna hate, ain'ters gonna ain't.

А на чём ещё вы писали? Вам есть, с чем сравнивать?

Примерно всё, что угодно, лучше C и C++.

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

Аналогично, по моему мнению — Rust для низкоуровневой ерунды, Agda или Idris для доказательств, Haskell для всего остального.

Потому что запихать в докер что-то другое невероятно сложно

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


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

Был на практике противоположный случай, гошное приложение запускало контейнер с нейросетью, в котором был питон, луа, что-то, написанное на плюсах, скармливало ему фотки, парсило вывод и отдавало клиенту.

Ещё сейчас у нас, например, в виндовом контейнере работает агент тимсити, собирает плюсовый и шарповый код.

Настолько популярный, что на вакансию middle на 150-200к никто не откликается. В отличии от условного ts/react =)

Вероятнее всего в вашей вакансии требуется не только Go и опыт коммерческой разработки наверняка от 3 лет. Может просто стоит немного снизить требования. При этом можно будет снизить и зарплату. И поднять ее тогда, когда человек дорастет до требуемого уровня.
Откуда браться мидл разработчикам, если все хотят только мидлов? Нашел только одну более менее вменяемую вакансию для джуна на весь Питер. На тот же TS/React джунов берут с большей охотой. Поэтому и проблем с ними нет.
Откуда браться мидл разработчикам, если все хотят только мидлов?
В случае с Go — свитчиться с других языков. Как показывает практика, те же сишники и плюсовики осваивают Go чуть ли не за день и вообще без каких-либо сложностей, при этом отдыхая душой и мозгом как в санатории.

Подумывал о свитчинге, но в попадающихся вакансиях на миддлов опыт разработки на Go год минимум хотят.

Как правило, 90% пунктов в вакансиях означает не «must have», а «неплохо бы иметь, но если не, то все равно обсудим варианты» :)
А в случае с Go и наличием большого опыта на других языках, всегда можно показать какой-нибудь пет-проектик — мне несколько рекрутеров стучались, увидев небольшую утилитку на Go в гитхабе, и даже когда я в ответ говорил что кроме этой утилитки на Go я в жизни ничего не писал на тот момент, все равно предлагали пособеседоваться.

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

Это понятно, что с других языков можно быстро переключиться. Но часто в вакансиях требуют не просто опыт программирования, а опыт программирования именно на определенном языке, а то и в определенном стеке технологий. И это сильно сужает круг тех, кто мог бы откликнуться на вакансию. Тем более если человек уже является мидлом и имеет сравнимый доход, то стимулов куда-то перебегать у него не особо много. То что проще писать, сомнительный плюс. Людям обычно проще писать на том, к чему они привыкли, даже если объективно их родной язык достаточно сложный. Переучиваться бывает тупо лень.
Вот для новичков в программировании перспектива подобной зарплаты и востребованность — это стимул и большой стимул. Только, получается, что все равно придется через другие языки заходить, так как джуны на Go никому не нужны. Сам готов изучать Go, но куда с ним потом идти без опыта, вопрос.
Сам готов изучать Go, но куда с ним потом идти без опыта, вопрос.

Я не гошник, но, кажется, сейчас с вакансиями на Го проблем нет. Да и большие компании его активно используют (Яндекс, Касперский, Авито и т.д.). И в целом есть ощущение, что на Го сейчас пишутся интересные и современные проекты, в отличии от того же C++, где хорошие деньги, как правило, предлагают в основном за ковыряния в legacy.

Там ключевое "без опыта"

Перечитайте мой пост выше. Нашел лишь одну вакансию, куда я могу кинуть резюме, не имея опыта коммерческой разработки на Go и где не требуют знания еще пары языков в придачу. В принципе это приемлемо, если полагать, что я буду единственным человеком в городе, решившим изучать Go, и если эта вакансия будет открыта и через полгода, когда я буду уверено себя чувствовать с самим Go, gRPC, который нужен практически в обязательном порядке и PostgreSQL. Но надежда на такое стечение обстоятельств стремиться к 0.
Вы правы, что проблем с вакансиями Go нет проблем, но просто посмотрите требования, 99,99% из них точно не для начинающих или тех, кто решил с фротенда перейти в бэкенд.
при этом отдыхая душой и мозгом как в санатории

Это аргумент! :) Нужно будет при случае почитать документацию по этому Go. :)

ну мы ж не знаем, может у вас там филиал сималенда

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


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

как-то маловато для миддла
Можно ссылочку на вакансию?
Go — хороший язык. Действительно простой. Среди нового поколения языков {Go, Rust, Swift}, переосмысливающих наследие C/C++, пожалуй самый простой (а Rust самый сложный).
А скоро наконец-то добавят дженерики, которых так не хватало программистам (если я правильно понимаю, это основная претензия к Go).
А скоро наконец-то добавят дженерики

… и вот уже язык не такой уж и простой.

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

Сложность языку придаёт добавление в него ЛЮБЫХ языковых конструкций. Или у вас какое-то другое мерило сложности языка?

В brainfuck восемь команд, это простой язык?

Почему вы отвечаете вопросом на вопрос?

Я говорю: 2 меньше чем 4.
Вы спрашиваете: 2 — это мало?
Я без понятия. Я не знаю что вы понимаете под "мало".

Вы этого не говорили, а я этого не спрашивал.

Почему вы отвечаете вопросом на вопрос?

Видимо у вас сложно с элементарными логическими цепочками. Неудивительно, что вам нравится Го.


Потому объясню. Рассмотрим вашу гипотезу:


  1. Вы утверждаете, что "Сложность языку придаёт добавление в него ЛЮБЫХ языковых конструкций"
  2. Следовательно чем больше языковых конструкций — тем сложнее язык.
  3. Следовательно чем меньше языковых конструкций — тем проще язык

Вашу гипотезу можно проверить. Берём полный по Тьюрингу язык с малым количеством языковых конструкций. Идеально подходит Brainfuck, как язык всего с 8 конструкциями. Но, увы, он сильно сложнее других языков с большим количество конструкций.


Следовательно, исходное предположение было неверным. Количество языковых конструкций не может быть мерилом сложности языка.

Идеально подходит Brainfuck, как язык всего с 8 конструкциями.

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

вот это поворот!
«Вашу гипотезу можно проверить. Берём полный по Тьюрингу язык с малым количеством языковых конструкций. Идеально подходит Brainfuck, как язык всего с 8 конструкциями. Но, увы, он сильно сложнее других языков с большим количество конструкций.»
Вы так лихо придали ему сложности — только одним простым своим утверждением об этом, противореча вами же установленному п.1
Видимо это у Вас сложности с логическими причинно-следственными связями.

Доказательство того, что брейнфак — сложный нужно смотреть на странице 74.

Неудивительно, что вам нравится Го.

Го мне отвратителен, и вообще это ad hominem.


Рассмотрим вашу гипотезу

Это не гипотеза, а моё определение что есть "сложность языка". Я допускаю наличие других определений, но их предложено не было.


Но, увы, он сильно сложнее других языков с большим количество конструкций.

Вы утверждаете что он сложнее, но так и не привели ваше определение сложности языка.

Го мне отвратителен, и вообще это ad hominem.

Это была очевидная шутка, а не ad hominem.


Вы утверждаете что он сложнее, но так и не привели ваше определение сложности языка

Сложность — определение комплексное и субъективное. Я, к сожалению, не могу привести свою метрику, но напрямую оценивать сложность в количестве конструкций ошибочно.

Сложность — определение комплексное и субъективное.

Тогда какого простите чёрта вы объявляете МОЁ субъективное определение ошибочным?


И я вообще не понимаю как определение термина может быть ошибочным.

Как язык Brainfuck абсолютно тривиален, сложность в написании на нём отличных от "Привет, мир!" программ.


Хотя тут, конечно, возникает вопрос: а что есть сложность языка, если не сложность работы с ним?

Да можно углубиться еще сильнее, mov — тьюринг-полный оператор, который может все. Всего один оператор! В 8 раз меньше чем у брейнфака!

Я считаю что очень простой язык. На нем сложно писать большие программы, но язык-супер простой.

Среди нового поколения языков {Go, Rust, Swift}, переосмысливающих наследие C/C++, пожалуй самый простой (а Rust самый сложный).

Мне нравится, что Rust много подчерпнул из мира ФП и продолжает продвижение в эту сторону, пусть и немного косо (GAT'ы). Но он сильно системный. Не думаю, что для прикладного программирования всякого веба так необходимо управлять временм жизни каждого объекта, GC is fine too. Какой-нибудь Rust с GC (абсурдно, да) был бы очень крутым языком для этих целей.


ЗЫ. Линейные типы (ну или почти линейные типы) в Rust помимо управления памятью еще и безопасную конкруентность обеспечивают.

Какой-нибудь Rust с GC (абсурдно, да) был бы очень крутым языком для этих целей.

Rust с GC был бы не Rust.

Изначально в Rust был встроенный GC, и есть разные варианты GC для Rust.

Изначально в Rust был встроенный GC...

А также зелёные потоки. Но до выхода Rust 1.0 их все выпилили.


… и есть разные варианты GC для Rust.

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

Можно иметь два типа указателей, одни мусоросборные, другие без. Как в мелкомягком c++/cli… а какие-нибудь другие языки с такой фичей есть?

Какой-нибудь Rust с GC (абсурдно, да) был бы очень крутым языком для этих целей.

D (Dlang)

Но сравнивая с go, GC там будет блокировать на порядки дольше. И серьезные вещи на D пишутся переписывая стандартные библиотеки, что бы не использовать GC
И серьезные вещи на D пишутся переписывая стандартные библиотеки, что бы не использовать GC

А на Rust уже есть куча библиотек, которым не нужен GC ¯\_(ツ)_/¯

Я так понимаю имелось в виду на D
Кому надо — делают… как мы.

Есть достаточно большая наша библиотека(в основном реактор и все вокруг), которую мы недавно передали в dlang-community
Нам для файловой системы GC ну никак не нужен. И с кернел-ом разговаривать нужно только ну в крайнем случае.
github.com/dlang-community/mecca

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

Мы правда не все туда перенесли, большАя часть того что могло быть вынесено не вынесли, но решили отдать как есть. Еще месяц назад она сидела на нашем github аккаунте, сейчас перенесли в dlang-community, так как появились желающие подхватить.
Какой-нибудь Rust с GC (абсурдно, да) был бы очень крутым языком для этих целей.

Хаскель?


А, не, хаскель ленивый. Идрис? Хаскель с -XStrict?

Идрис?

Хорош, но банально не готов. И экосистема, считай, нулевая.


Хаскель с -XStrict?

Вот когда нормально линейные типы завезут (и желательно вариант Prelude с ними) — тогда и поговорим.

Хорош, но банально не готов. И экосистема, считай, нулевая.

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


Вот когда нормально линейные типы завезут (и желательно вариант Prelude с ними) — тогда и поговорим.

9.0.1 недавно зарезилизся. Там вроде нормально сделано (но я ещё всерьёз не ковырял).


Prelude вообще выкинуть к хренам надо, а не завозить её линейную копию, но этого, увы, не будет. Поезд «avoid (success at all costs)» уехал.

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

Это смотря с чем сравнивать. Там система типов более сильная, чем во всяких C++ и прочих C#. Имхо, конечно, я даже не знаю, можно ли это как-то формально обосновать. Ну, хотя бы отсутствие неявного null.

отсутствие неявного null

Я, конечно, на Go писал совсем чуть-чуть, но разве все эти бесконечные if err != nil не противоречат сказанному?..

Прошу прощения, значит, не так понял (комментарий товарища 0xd34df00d относился, кмк, к Go, а не к Rust).

Система типов считается "сильнее", если с её помощью можно выразить больше свойств и инвариантов. В этом плане система типов Go не может быть сильнее ни C++, ни C#. Добавление дженериков поправит ситуацию, но отставание всё равно останется. Особенно с учётом костыля type lists вместо добавления интерфейсов-констрейнтов для встроенных операций и встроенных типов.

Достали статьи про «простой Go». Надо понимать, что Python сложный в сравнении с ним?

Про легко читается — я вообще не понимаю как эту кашу можно осмыслить. Да и судя по количеству публикаций пик популярности Go уже пройден года полтора назад. Кто сейчас какой нибудь Groove вспомнит? Тоже самое с Go уже скоро будет.
Python'у бы не мешало хотя-бы работу с переменными у Go позаимствовать:) Один лишний символ (операция := вместо =) и сразу объявление новой переменной становится отличимым от использования ранее определенной. Куча проблем исчезает сразу же.
Прям как будто Паскаля нет
В Паскале для объявления переменных требуется отдельный блок, то есть там нет важнейшего достижения С++ и множества языков, перенявших эту возможность — объявления переменных в любом месте программы.
Операция := действительно применялась в Паскале как присваивание, но внешний вид — это все что у нее общего с одноименной операцией в Go:) В Go это именно моментальное создание объекта с автовыведением типа почти в любом месте программы.
a:=10; // объявление
b:=20; // еще объявление
x:=a+b; // объявление и использование
a=30; // использование ранее определенной перменной
y:=a+b;// снова объявление и использование
x:=40;// ОШИБКА, переменная уже определена ранее
z=50; // ОШИБКА, переменная не определена

Кстати, будь разработчики Go посмелее, они могли бы пойти еще дальше и разрешить объявление переменных прямо внутри выражений. Получилось бы весьма по-хакерски:
x:= a * (s := b + c);
$x = $a * ( $s = $b + $c );

Совершенно корректный код в PHP.
Есть некие сомнения, что это можно сделать легко и дешево в языке со статической типизацией, но нет сомнений, что в принципе возможно.
Существенно то, что в PHP первое объявление переменной и ее использование не отличаются — а символ $ просто обозначает что это переменная. Вот если бы $ использовался только при первичном объявлении, а далее переменная использовалась бы без $, это было бы гораздо лучше, т.к. позволяло бы моментально находить опечатки в именах переменных.

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

А чем тут типизация мешает? Это исключительно вопрос синтаксиса.

Кстати, будь разработчики Go посмелее, они могли бы пойти еще дальше и разрешить объявление переменных прямо внутри выражений.
Отсутствие у выражений побочных эффектов — один из принципов написания надёжного кода.

Так что очень хорошо, что ничего подобного в Go нет. И ещё лучше, что авторы Go выбросили из языка всё C-style трюкачество, порождающее UB. В Go все виды присваивания, инкремент, декремент — операторы (как в Pascal), а не операции (как в большинстве C-style языков), и потому они не могут быть использованы внутри выражений.

Что за безграмотный коммент.
В большинстве C-style языков (Java, C#) инкременты-декременты не вызывают никакого UB, потому что порядок вычисления регламентирован (слева направо). В C/C++ просто хотели оставить максимальную свободу для реализации компилятора и оптимизатора.

Предельно упрощенный пример:
int q = 5, r = 5;
q += q++;
r += ++r;
Чему равны значения q и r? В разных языках программирования мы получим разные ответы и все они будут полностью обоснованными.

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

это да, но к UB — Undefined behavior — не имеет никакого отношения

x:= a * (s := b + c);

Да да! А потом стрелочные функции, краткие if условия..., которыми надо затыкать любую дыру. И, конечно, больше скобок! Весь код должен умещаться в одну строку… Привет, js, ололо

Я бы согласился насчет того, что это огого как круто (про объявление), но не получается.
Вот это объявление в любом месте — компилятору на него плевать, оно только для людей же. Место-то он выделит там же, где и паскаль, при входе в блок (ну ок, у нас — в функцию), нет?
А проблему человека, которому не хочется откатываться наверх, легко решает IDE, shift-ctrl-c на необъявленной переменной — и объявление переменной автоматически уезжает в ближайший var, в зависимости от типа того, что ты ей присваиваешь.
и объявление переменной автоматически уезжает в ближайший var, в зависимости от типа того, что ты ей присваиваешь.
И возникают две проблемы:
  1. Оно оттуда не исчезает, даже если переменная не нужна.
  2. Переменную можно легко поиспользовать до инциализации.
И да, конечно, можно в IDE накостылить ещё и ещё решений для этих проблем… но зачем, если можно от этого избавиться на уровне языка?

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

Различие между a := foo(…) и a = foo(…) очень пользительно в Go именно для человека.

Первое — это просто сохранение значения в переменную. Простая операция, думать не о чем. Второе — это изменение переменной… операция более сложная, опасная и сразу привлекает внимание.
Философия Питона отличается.
Динамическая типизация, объявление переменных — суть одна, упрощение.
Ругать или хвалить бесмыссленно.
На Питоне не писал, но писал немного на PHP — там такая же философия. И для меня это самое неудобное и постоянно приводившее к ошибкам — опечатки в именах переменных или просто забывчивость. Никакого упрощения, одно усложнение — нужно СУПЕР ВНИМАТЕЛЬНО смотреть код, буквально вчитываясь в каждую букву и удерживая в голове всё то, о чем в компилируемых языках просто не задумываешься, чтобы понять почему работает не так как ожидалось.
Ну фиг с ней, с типизацией. Но явное объявление имен переменных должно быть!

Вот ты где, любитель засрать весь код final / const кейвордами. Я долго тебя искал

Вообще не любитель такого:)
Ну если язык не даёт то же самое лаконичнее сделать — то да, будут final и const

Современные статанализаторы, включая встроенные в IDE и работающие без явного запуска, такие ошибки отлавливают на ура.

А я не профессионал, а любитель. И писал что-то по быстрому для себя, или может изучал и экспериментировал. Среда разработки вроде была Notepad++ :)
У присваивания "=" есть только один минус — он может случайно использоваться в условии. Но современные IDE сразу об этом предупреждают. Вирт, когда об этом писал полвека! назад, таких редакторов не имел.
В Go присваивание ":=", но при этом можно осуществлять переопределение переменных во внутренних блоках, — а все потому что кое-кто думал задницей, а не головой. В результате теперь можно совершенно случайно объявить новую переменную в внутреннем блоке, вместо присваивания. Ошибка совершается часто, а искать ее очень муторно, IDE ее не показывают, т.к. выглядит это как сознательное решение программиста.
Да, Python — в сравнении с Go — очень сложный язык.

Количество синтаксических конструкций в Python разы больше, чем в Go. Сами эти конструкции нередко противоречат здравому смыслу: в генераторах Python переменная цикла используется до заголовка цикла. Слабая типизация: к логическому типу автоматически приводится практически что угодно. Отсутствие контроля типов, порождающее трудно обнаруживаемые ошибки…

P.S. Значительная часть конструкций Python — синтаксический сахар, не добавляющий языку никаких новых свойств. То, что язык позволяет одно и то же делать десятью разными способами — это не достоинство, а недостаток.

Несмотря на наличие кучи сахара, питон легко освоить, а код на нем легко писать. На мой взгляд, рост скилла питониста касательно именно самого языка, заключается в каких-то подкапотных особенностях а не в освоении синтаксиса и стандартной библиотеки. А свои нюансы есть от Си до C#, про тот же Go на хабре в комментах читал тред про UB, в котором Go'шник аргументировал "ну, этот момент нужно знать".


Из того, что прям люто-бешено не люблю в Python — это динамическая типизация и интерпретируемость. С другой стороны, есть C#, который в выразительности не сильно уступает питону, если речь не заходит про всякую математику, потому что numpy, scipy, pandas и прочий matplotlib в экосистеме C# не хватает. И слайсов.

Да, писать на Python легко. Но искать ошибки в написанном коде — сложно. И даже понять, что была допущена ошибка, бывает сложно.

Качественный код — это, в том числе, код, который легко читать. Но чаще бывает так, что чем легче код писать, тем сложнее его потом читать.

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

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

P.S. Любители ставить знак равенства между краткостью и выразительностью почему-то забывают, что написанный код будут читать и модифицировать другие люди. И чем больше используется трюков (в том числе и синтаксического сахара) для уменьшения длины кода, тем сложнее этот код понимать. Чтобы понять 3 строчки APL-кода (а это самый «выразительный» из существующих языков) может понадобиться несколько часов.

А сколько лет надо читать Go код, чтоб мозг начал фильтровать if err != nil блоки через каждую значимую строку логики? )

err != nil — это не «через каждую значимую строку логики», а завершитель «значимой строки логики»:
if res, err = функция_реализующая_этап_логики(params); err != nil {
И это отлично читается. Куда проще и быстрее, чем разбирательство по коду, откуда именно в данный catch будет приходить исключение, когда оно генерируется 10 уровнями вложенности глубже.
Куда проще и быстрее, чем разбирательство по коду, откуда именно в данный catch будет приходить исключение
Ctrl+Click по типу исключения в кэтч-блоке, смотрю использования в коде проекта, радуюсь. А ещё есть Either и прочие радости жизни.
Полагаю, вам никогда не приходилось читать напечатанные на бумаге листинги хотя бы на тысячу строк кода? IDE — это очень удобно. Но есть она далеко не везде и не всегда.
Если вам нужно без IDE и тем более по распечатке не только читать код, а ещё и искать источники исключений, то менять надо не ЯП, а работу (на ту, где такого трэшака не происходит), мне кажется.

Если чуть менее ехидно, то такая необходимость — это такой эдж-кейс, что использовать его в общем обсуждении как аргумент кажется крайне странным. С тем же успехом можно сказать «Х — плохой язык, потому что вот в моей компании СБ его ещё не одобрила».
Если я могу за разумное время разобраться в коде на языке X только используя IDE, то X — плохой язык.

Повторю свою мысль другими словами: бывают ситуации, когда доступа к IDE нет, а код править необходимо — причём срочно. И если из-за отсутствия IDE время на анализ и исправление кода увеличивается на порядок — в топку такой язык.
  1. за все свое время я ни разу не был без доступа к коду. Я понимаю ситуацию есть доступ к коду но нет к проду, но наоборот это бред.
  2. Хочу я посмотреть как вы без ИДЕ будете искать кто реализует данный интерфейс, когда в Джаве вы легко через имплемент найдете.
1. Какое отношение «доступ к коду» имеет к наличию IDE? Речь идёт только о невозможности использовать IDE здесь и сейчас. Например, в отпуске на Android-планшете. А с доступом к коду — по SSH — всё в порядке.

2. Причём здесь интерфейсы и Java? Речь идёт только о сравнении немедленной обработки ошибки-значения в Go и пробросе прерывания через десяток уровней вложенных вызовов (без какой-либо привязки к конкретному языку). Именно в последнем случае приходится вести раскопки в файлах.
Речь идёт только о невозможности использовать IDE здесь и сейчас.

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

сравнении немедленной обработки ошибки-значения в Go и пробросе прерывания через десяток уровней вложенных вызовов
Вы-таки знаете, пока я работал Го-девелопером (полтора года) и много шарился по чужим — включая крупный опенсорс — исходникам, в 9 случаях из 10 так называемая «немедленная обработка ошибки-значения» выглядит, как
if (err != nil) return nil, err // и так глубиной в 10 вызовов в библиотеке 
То есть тот самый «проброс», за который вы хаете джаву. И да, трай-кэтч никто не мешает тоже так же тут же сделать.

Причём здесь интерфейсы и Java?
Притом, что сравнивали вы с бросанием исключений в джаве, а сравнение это использовали, как аргумент вот для этого:
И если из-за отсутствия IDE время на анализ и исправление кода увеличивается на порядок — в топку такой язык.
И на что вам сказали, что из-за убожества интерфейсов Го ещё хуже подходит для разбора без ИДЕ.
Вам того же искренне советую, решит выдуманную вами проблему на корню.
То, что я привёл абсолютно реалистичный сценарий, вы, разумеется, «не заметили».
Притом, что сравнивали вы с бросанием исключений в джаве, а сравнение это использовали, как аргумент вот для этого:
У вас слишком богатая фантазия. В этой ветке обсуждения я название «Java» не использовал. От слова «совсем». Так что вы выдумали у себя в голове картинку, не имеющую никакого отношения к моим текстам и теперь эту воображаемую картинку пытаетесь «опровергнуть».
Вы привели абсолютно реальный сценарий, который всего-то лишь значит, что если вы можете в нём оказаться, надо иметь с собой ноутбук, да. Потому что Го для описанного сценария подходит так же плохо, как любой другой язык, см. всё остальное сообщение и всю ветку выше.

Хорошо, про джаву я действительно ошибся, извините. Проассоциировал ваше упоминание catch с ней.
Подставьте название другого языка, использующего try/catch, суть не изменится. То, что я упомянул проблемы конкретно го-кода, которые в рамках ваших же утверждений делают Го «плохим» языком, вы, разумеется, «не заметили». :)

Так, ээээ, го в целом ещё хуже той же джавы в этом смысле, как минимум из-за ущербной системы типов, придурочной утиной реализации интерфейсов, совершенно невыразительного кода в целом и обработки ошибок в частности.

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

По поводу обработки ошибок я уже писал выше и не вижу смысла повторяться.

Что качается утиной типизации, то этот вопрос не столь однозначен: утиная типизация и создаёт проблемы, и добавляет удобства. В общем, все более-менее объективные обсуждения этой темы сводятся к мысли о том, что молотком можно и забить гвоздь, и ударить по голове соседа.

Ещё и два оператора в строке… Разрешено в принципе или для этого конкретного случая в строгом встроенном линтере Go исключение?

Это не «два оператора в строке», а формат операторов if и switch в Go:
if [инициализатор;] логическое_выражение {
switch [инициализатор;] выражение {

Почему вас не смущает
for ([инициализатор] ; [логическое_выражение] ; [выражение])
в большинстве C-style языков, но смущает наличие инициализатора в if и switch в Go?

Такой формат if и switch — вкупе с возможностью возвращения нескольких значений из функций — существенно повышает удобство как написания, так и чтения Go-кода.

Очень не очевидная семантика — что в условии-то инициализировать обычно? Особенно без скобок, без определения области "видимости". Всегда воспринимал ее просто как два оператора, пускай и странных каких-то и писал их нормально, даже исправляя IDE, чтобы не вызывать у коллег, читающих мой код, это чувство странности. Ну да, с таким необходимым количеством if'ов без этого совсем плохо было бы. Хотя в большей части туториалов оно не употребляется, как и половине, наверное, кодовых баз куда я заглядывал

Любители ставить знак равенства между краткостью и выразительностью почему-то забывают, что написанный код будут читать и модифицировать другие люди. И чем больше используется трюков (в том числе и синтаксического сахара) для уменьшения длины кода, тем сложнее этот код понимать. Чтобы понять 3 строчки APL-кода (а это самый «выразительный» из существующих языков) может понадобиться несколько часов.

Золотые слова…

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


Какие-то питоновские слайсы и генераторы списков могут поставить в ступор того, кто из никуда не видел, но человек с опытом в питоне с лёгкостью их прочтет.


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

C#
Рассмотрим обращение к полю, на пути к которому может встретиться несколько null'ов


var bar = repo.FindUser()?.GetFoo()?.GetBar();
if (bar != null) ...

// vs

var user = repo.FindUser();
if (user != null) {
    var foo = user.GetFoo();
    if (foo != null) {
        var bar = foo.GetBar();
        if (bar != null) ...
     }
}

Другой пример. Например, Go использует возвращаемые значения, а не исключаэения. Возвращаемые значения — это хорошо, но не так, как это сделано в Go (имхо). Обработка ошибок в Go похожа на старую-добрую портянку if'ов из Си (правда, в Си есть популярный пвттерн с goto, не уверен, есть ли аналог в Go). В то же время в том же Haskell так же используются возвращаемые значения (монады Maybe, Either) вместо исключений, но языки представляет do-нотацию, позволяющую писать оборотку ошибок (и вообще любой монадические цепочки bind'ов) куда проще. При этом для незнакомого с Haskell человека do-нотация буклет менее очевидна, чем ифы. Rust, полагающийся на аналогичный механизм обработки ошибок, использует специализированный костыль специально для Result: "?" оператор.


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

правда, в Си есть популярный пвттерн с goto, не уверен, есть ли аналог в Go

Насколько мне известно, в Go в "кишках" библиотек любят использовать panic-recover, которые по сути throw-catch.

Какие-то питоновские слайсы и генераторы списков могут поставить в ступор того, кто из никуда не видел, но человек с опытом в питоне с лёгкостью их прочтет.
Только вот генераторы списков (словарей, множеств) нужны только для того, чтобы сэкономить несколько символов в коде. Причём часто это экономия на спичках, требующая запоминания дополнительных языковых конструкций.
Обработка ошибок в Go похожа на старую-добрую портянку if'ов из Си
Разница между C и Go в том, что функция C может возвращать только одно значение примитивного типа (включая указатель). В C одновременный возврат и значения, и информации об ошибке — это всегда геморрой. А при необходимости штатного проброса ошибки через несколько уровней вызова — ручные setjmp / longjmp.

Go в этом отношении намного гибче и удобнее. Кроме того, синтаксис заголовков if, for и т.д. позволяет делать проверки ошибок намного удобнее как для пишущего, так и для читающего код.
Рассмотрим обращение к полю, на пути к которому может встретиться несколько null'ов
Я понимаю, зачем операцию "?." встраивают в современные языки. Относится ли она к синтаксическому сахару — не знаю, однозначного мнения у меня нет. Так ведь можно дойти до того, что и операцию изменения знака числа синтаксическим сахаром объявить: ведь можно же отнять от нуля.

Но, думаю, и вложенные if'ы, и обсуждаемая операция — это далеко не единственные варианты решения проблемы null-цепочек. В том же Go возможно вызвать метод для nil, если этот nil — указатель на типизированное значение. Так что вполне можно написать:
bar := repo.FindUser().GetFoo().GetBar()
, а проверку на nil вынести (да, if'ами) в методы FindUser, GetFoo, GetBar:
func (user *User) GetFoo() *Foo {
    if user == nil {
        return nil
    }
    return user.foo
}
Другое дело, что в таком виде это не совсем типичный для Go подход. ИМХО, подобный код чаще встречается в Middleware — когда каждый обработчик начинает с проверки наличия ошибок в контексте.
Только вот генераторы списков (словарей, множеств) нужны только для того, чтобы сэкономить несколько символов в коде.

Сэкономить относительно чего? Если относительно обычного for, то далеко не несколько символов.


Генератор для примера


>>> [x * x for x in range(10) if x % 2 == 0]
[0, 4, 16, 36, 64] 

Цикл for:


  • он куда более громоздкий
  • он банально медленнее из-за постоянных append
  • в отличие от генератора, это не выражение, поэтому не получится использовать в друигх выражениях, в return и так далее

squares = []
for x in range(10):
    if x % 2 == 0:
        squares.append(x)

Теперь сравним с обычными функциями высшего порядка. Это еще более нечитаемая конструкция.


>>> list(map(lambda x: x * x, filter(lambda x: x % 2 == 0, range(10))))
[0, 4, 16, 36, 64]

Лично я не "топлю" за генераторы списков, словраей итп, просто в питоне в связи с остальными особенностями дизайна и стандартной библиотеки питона, это наиболее простой, локаничный и читаемый способ делать многие вещи. Можно сравнить с C# LINQ, который, на мой взгляд, еще лучше:


IENumerable.Range(10).Where(x => x % 2 == 0).Select(x => x * x);

Имеет почти все плюсы генераторов, но не вносит какого-то особенного синтаксиса. Правда, шарп немного громозкий тем, что там нет отдельно стоящих функций (IEnumerable.Range() vs range()), но это нюансы.


Разница между C и Go в том, что функция C может возвращать только одно значение примитивного типа (включая указатель). В C одновременный возврат и значения, и информации об ошибке — это всегда геморрой.

Разве?


Foo foo;
if (bar(&foo) != 0) {
    // handle
}

Для меня не сильно отличается от примера ниже, может, я просто не понимаю.


foo, err := bar()
if (err != nill) {
   // handle
}

В том же Go возможно вызвать метод для nil, если этот nil — указатель на типизированное значение.
func (user *User) GetFoo() *Foo {
if user == nil {
return nil
}
return user.foo
}

ИМХО, но


  • это меняет поведение функции ради обработки ощибок в вызывающем коде, несколько нарушает принцип единственной ответственности
  • это делает функцию по-умолчанию null-friendly, что может отстрелить ноги где-то там, где мы не ожидаем null. Вкусовщина, но предпочитаю явную nullability, к которой в последнее время стремятся многие языки (C#, Dart запилили, Kotlin, вроде, by design, насколько это возможно в JVM, Rust by design...)
Сэкономить относительно чего? Если относительно обычного for, то далеко не несколько символов.
Вы выбрали забавный пример. Если привести его в нормальный вид, разница окажется куда меньше:
squares = [x * x for x in range(0, 10, 2)]
# против
squares = []
for x in range(0, 10, 2): squares.append(x * x)
# против
squares = list(map(lambda x: x * x, range(0, 10, 2)))
И если в Python append в цикле существенно медленнее генератора, возникают вопросы к авторам языка, которые с каждой версией впихивают в язык новые рюшечки, но при этом забили на оптимизацию скорости работы интерпретатора.

Ваш исходный пример с двумя лямбдами хорошо показывает, во первых, крайне неудачный синтаксис лямбд и, во вторых, то, что генератор является уродливым гибридом map и filter. Вместо того, чтобы реализовать нормальные методы map, filter, reduce для каждого типа коллекций, сделаны странные функции map и filter, возвращающие не коллекции исходного типа, а особые объекты, reduce же из Python 3 вообще выпилили. И всё это приправлено генераторами.

N.B. Не люблю JavaScript, но map / filter / reduce сделаны в нём намного удобнее, чем в Python или PHP. Предпочёл бы в Python вместо генераторов иметь возможность написать:
squares = list(range(0, 20, 2)).map(x => x * x)
# или даже
squares = list(range(20)).filter(x => x % 2 == 0).map(x => x * x)

Foo foo;
if (bar(&foo) != 0) {
    // handle
}
Для меня не сильно отличается от примера ниже, может, я просто не понимаю.
Здесь не возврат двух значений, а возврат одного числа (кода ошибки) и побочный эффект в виде изменения значения переменной foo, адрес которой передан в качестве параметра.

А теперь представим, что foo — глобальная переменная и используется внутри bar совсем не для тех целей, для которых используется передаваемый параметр. В результате bar по факту работает с одной переменной, хотя по логике должно быть две разных.

Та же самая проблема может возникнуть при передаче в функцию нескольких указателей.

Разумеется, такая опасность существует в любом языке с передачей параметров по ссылке (передаче указателя). Подход Go, возвращающего все результаты явно, а не через побочные эффекты, уменьшает кол-во параметров-указателей и тем снижает риск подобных коллизий.
это меняет поведение функции ради обработки ощибок в вызывающем коде, несколько нарушает принцип единственной ответственности
Это просто пример, демонстрирующий одно из качественных отличий Go от большинства топовых языков: в Go можно вызвать метод для указателя, значением которого является nil. И это меняет подход к построению nullability-кода по сравнению с другими языками.

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


Вместо того, чтобы реализовать нормальные методы map, filter, reduce

Я согласен, что это кривой дизайн, что в питоне это функции, а не методы. В хаскелле это было бы удобно, там есть особый сахар для композиции функций, а тут нет.


возвращающие не коллекции исходного типа, а особые объекты

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

И если в Python append в цикле существенно медленнее генератора

Насколько я понимаю…
Append — это универсальная функция добавления в лист. Лист в питоне — по-сути, вектор, значит, append может привести к реаллокации памяти, во не быстро. А генератор — это внутри цикл с yield. Поэтому


  • цепочка операций над генераторами вообще не потребует аллокаций промежуточных промежуточных листов
  • видимо, в интерпретаторе есть оптимизации по созданию листа из генератора, по сравнению с созданием русски ручного добавления элементов. Вообще, тут я могу быть не прав.
функция C может возвращать только одно значение примитивного типа (включая указатель). В C одновременный возврат и значения, и информации об ошибке — это всегда геморрой.
Я может конечно чего-то не понимаю...
struct res
{
    int val,err;
};

struct res megafunc(/* ... */) {
    struct res r;
    // ...
    return r;
}
Прошу прощения, отстал от жизни. В тех компиляторах, которые я использовал 30 лет назад, такого функционала не было. Не подскажите, в каким именно стандарте появилась эта возможность?
Не подскажите, в каким именно стандарте появилась эта возможность?
В самом первом. ANSI C89. Догадайтесь какой год.

Специально потратил полчаса и проверил: Turbo C 1.0 1987го года так действительно не умеет, а вот Turbo C 1.5 1988го уже так вполне.

P.S. Исправление: в Turbo C 1.0 проблема с парсером. Нужно использовать традиционное:
typedef struct res
{
    int val,err;
} res;
И не struct res потом, а просто res. Тогда работает. Впрочем вопрос “а что там было в разных компиляторах C в 80е до появлени стандарта”… он ну оооочень имеет мало отношения к сегодняшнему программированию.
И чем больше используется трюков (в том числе и синтаксического сахара) для уменьшения длины кода, тем сложнее этот код понимать. Чтобы понять 3 строчки APL-кода (а это самый «выразительный» из существующих языков) может понадобиться несколько часов.

Не всегда. Какое-нибудь


preservation : ε ↝ ε'
             → Γ ⊢ ε ⦂ τ
             → Γ ⊢ ε' ⦂ τ

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


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

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

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

Тогда язык С проще их обоих.
Скинь мне сниппет кода на Go, я перепишу его на Python. Бросаю тебе вызов ;)


в генераторах Python переменная цикла используется до заголовка цикла

Например?


Слабая типизация: к логическому типу автоматически приводится практически что угодно. Отсутствие контроля типов, порождающее трудно обнаруживаемые ошибки…

Автоматическое (неявное?) преобразование чего угодно к bool происходит только в условных выражениях. Сделать True + [] не получится. Я могу вспомнить несколько случаев неявных преобразований, причем ни одно из них не создавало проблем:


условные выражения и bool: not x, x or y
числа и bool: 5 + True
числа между собой: 10 + 10.5 + 2j
форматированные строки: f'abc: {None}'


В остальном неявные преобразования запрещены для несовместимых типов.

Сделать True + [] не получится.

Зато получится сделать True + 10.


условные выражения и bool: not x, x or y

Неочевидно, чему равен результат x or y при x ~ Falsey или bool(y). Не уверен, что это можно вывести из первых принципов, поэтому это надо просто запомнить.


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

Зато получится сделать True + 10.

И то, потому что bool является подклассом int — этот компромисс сложился исторически. Хотя я не встречал ни одной ошибки, которая бы следовала из этого.


Неочевидно, чему равен результат x or y при x ~ False — y или bool(y). Не уверен, что это можно вывести из первых принципов, поэтому это надо просто запомнить.

Равен y, оператор or возвращает операнд по его истинности. К слову, о каких принципах речь?


Можно, конечно, начать обмазываться всякими mypy, но у них довольно малая выразительная сила

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


непонятно, почему бы не взять сразу нормально типизированный язык.

Какой из них и почему?

Равен y, оператор or возвращает операнд по его истинности.

Это понятно, но если вы не работаете с питоном постоянно, то для этого надо запустить репл и проверить, что False or 10 вернёт 10, а не True.


К слову, о каких принципах речь?

Ну, чисто теоретически, если мы рассматриваем типизированный язык, и у функции or там тип Bool -> Bool -> Bool, то даже если там есть неявные преобразования, то понятно, что False or 10 вернёт True — ну не может он 10 вернуть, оно не влезет в тип.


Аналогично, если бы тип выглядел, пардон за псевдохаскелизм, как


or :: (Boolable ty1, Boolable ty2) => ty1 -> ty2 -> Either ty1 ty2

то понятно, что возвращается само значение — тут уже


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

Ну так C++ — далеко не образец прелестной системы типов.


Какой из них и почему?

Зависит от задач. Если нужна близость к железу (но тогда вообще какой питон?), то раст. Если нужно что-то быстро напрототипировать и есть нужные библиотеки (но это для любого языка важное условие), или, в зависимости от команды, просто умолчанию — то хаскель тот же. Если вы француз — то окамл.

Это понятно, но если вы не работаете с питоном постоянно, то для этого надо запустить репл и проверить, что False or 10 вернёт 10, а не True.

Такая аргументация работает в обе стороны. Если я не работаю постоянно с хаскелем (который для этого требует слома мозга), я понятия не имею что означает Bool -> Bool -> Bool.


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

Выбор невелик.

Такая аргументация работает в обе стороны. Если я не работаю постоянно с хаскелем (который для этого требует слома мозга), я понятия не имею что означает Bool -> Bool -> Bool.

Это означает функцию, которая принимает два Bool и возвращает Bool. Это вроде та часть, которая слома мозга не требует.


Выбор невелик.

Он на самом деле больше, но я о прочих решениях не осведомлён. Где-то люди и всякие там сишарпы с джавами и котлинами используют, но мои задачи с ними не пересекаются.

Не требовала бы если было (Bool, Bool) -> Bool, а то Bool -> Bool -> Bool выглядит не как функция, принимающая два Bool аргумента и возвращающая Bool, а то ли как функция принимающая Bool и возвращающая функцию, принимающую Bool и возвращающую Bool, то ли как функция, возвращающая Bool и принимающая функцию, принимающую Bool и возвращающую Bool. Тут даже язык сломать можно, не то что мозг ))

функция принимающая Bool и возвращающая функцию, принимающую Bool и возвращающую Bool

Так и есть. Это каррирование — функцию многих аргументов может предоставить как последовательность применения (?) функций одного аргумента. И да, без каких-то знаний код на хаскеле лично мне нечитаем.

И да, без каких-то знаний код на хаскеле лично мне нечитаем.
Я вам больше скажу. Если вы возьмёте код на любом языке, вот вообще на любом… и покажите его фермеру, который ничего никогда ни на каком языке не программировал… он в нём не разберётся.

А что хаскеллисту приятнее читается: (Bool, Bool) -> Bool или Bool -> Bool -> Bool? Логику каррирования я вроде понимаю, но второе как описание условной func(Bool, Bool) -> Bool не воспринимается.

В ныне забытом Clean для каррированных функций использовался синтаксис Bool Bool -> Bool, к слову.

Тут даже язык сломать можно, не то что мозг ))
В языке всё прекрасно. В Haskell любая функция принимает один аргумент и возвращает один аргумент.
Но поскольку всегда можно вернуть функцию, то два аргумента обрабатываются так: функция принимает один аргумент и возвращает другую функцию, которая принимает второй аргумент и уже возвращает окончаельный результат.
Но да, для этого немножко в математику нужно уметь.

Я про орган человеческий ) Его сломать можно, читая мой комментарий )


И это при том, что в математике функция нескольких аргументов вполне себе first class citizen.

И это при том, что в математике функция нескольких аргументов вполне себе first class citizen.

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

И это при том, что в математике функция нескольких аргументов вполне себе first class citizen.
Они там тоже ничем не отличаются от функций, которые возвращают функции.

Так что первый класс или второй — зависит от лектора.

Ну как не отличаются, если аргументов не один? То, что одно можно привести к эквивалентному второму, не говорит о том, что отличий нет.

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

Например те же вещественные числа можно ввести десятками разных способов (через набор аксиом, через “лучи” из рациональных чисел, через бесконечные последовательности нулей и единиц и прочая, прочая, прочая), но, тем не менее, их, обычно, не принято различать.

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

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

Вот вы сами говорите, что эквивалентные понятия — разные, одни удобны в одной ситуации, другие в другой

Определения разные. Понятие — одно.

Как правило для них даже имен разных не вводят.

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

Там вы можете использовать любое определение — какое вам больше нравится.

А у функций и понятия разные в случае одного аргумента и в случае двух.

Тогда язык С проще их обоих.
Проще, но не сильно: при создании Go из C выбросили препроцессор, цикл с постусловием, объединение, перечисление, битовые поля, тернарную операцию, адресную арифметику, явное управление памятью… Но C — системный язык, а Go и Python — прикладные.

Go — это C, модернизированный до прикладного языка, ориентированного на написание надёжного кода.
Скинь мне сниппет кода на Go, я перепишу его на Python. Бросаю тебе вызов ;)
Не сомневаюсь, что перепишешь (если не учитывать разницу в производительности кода) и даже без использования библиотек: если алгоритм реализован на одном ЯВУ, он может быть переписан на любом другом ЯВУ со схожей областью использования. И, полагаю, по кол-ву строк Python-код будет короче (хотя бы из-за отсутствия }). Но дальше код надо модернизировать, отлаживать…
Например?
[i for i in range(10)]

Автоматическое (неявное?) преобразование чего угодно к bool происходит только в условных выражениях
Случайно допустил опечатку в имени переменной и вместо переменной, содержащей логическое значение, в выражении оказалась переменная, содержащая строку. А потом два часа ищешь ошибку в коде. И в выражении 13 * True тоже ведь неявное преобразование типов. И тоже источник ошибок.

Либо мы говорим: «Python — язык с сильной типизацией» — и тогда никаких неявных преобразований (кроме, возможно, int -> float, только в одну сторону) в языке быть не может, либо перестаём врать и признаём, что типизация в Python совсем не «сильная».

О странности работы and и or (например, результат True and 13) выше уже вроде бы говорили. Это ведь очередной нарушающий логику синтаксический сахар и, одновременно, очередные грабли.
кроме, возможно, int -> float, только в одну сторону
Но ведь int не лезет во float! Только в double!
либо перестаём врать и признаём, что типизация в Python совсем не «сильная».
Я бы сказал, что она “не совсем сильная”.
Можно обусждать как именно мерить “силу типизации”, но это точно не bool (сильная и слабая).
Скорее это частично упорядоченное множество.
Речь идёт о Python, в котором нет понятия «double».
Дык в нём и понятие int тоже не такое, как в C. И да, вот такове вот целое число 12345678901234567890123456789012345678901234567890 (а оно таки типа int) в float в Python не хочет лезть тоже. 12345678901234567890123456789012345678901234567890 + 1.0 даёт 1.2345678901234567e+49.
Хабраглюк.
В питоне сильная типизация. Синтаксический сахар делает код боле выразительным, что несомненно является достоинством.
Я бы сказал, что она сильнее, чем в JavaScript, но кое-где слабее, чем хотелось бы (уж сколько историй про True/False было сложено), а кое-где — сильнее, чем хотелось бы (маниакальная упоротость на попытке разделить строки и массивы байт жутко мешает очень много где).
В Go она более практичная.

Python кстати очень сложный для продакшена, в т.ч. из-за своей динамической типизации. Хоть обвешайся линтерами и type hints, все равно что-то да вылазит в проде, типа что-то вдруг стало str, хотя всю жизнь было bool, и теперь падает.


А так, я тоже не вижу где в Go нашли простоту. Каналы, паники, контексты, json tags, всякие logrus/gorilla/errors тоже надо знать и уметь с ними работать, GOPATH/go mod. Наверное, у кого-то душевная травма после С++ templates, вот и любой язык без них кажется простым.

Python кстати очень сложный для продакшена, в т.ч. из-за своей динамической типизации

Это боль. Остаётся вопрос, как люди на JS пишут ещё больше, чем на питоне.

Надо понимать, что Python сложный в сравнении с ним?

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

Достали статьи про «простой Go». Надо понимать, что Python сложный в сравнении с ним?

В зависимости от требуемого уровня и задачи. Для уровня "написать коллетор данных с AWS, сгруппировать, сделать отчеты" проще python.
Для задачи "написать инфраструктурный микросервис для перекладывания событий в очереди и обратно" проще Го.


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

Во-первых, он Groovy, а во-вторых, на нем пишутся скрипты для gradle, и jenkins, так что он живее всех живых, причем в двух разных областях (gradle Андроид, а jenkins энтерпрайз).

Причем я бы сказал, что среди наших девопсов именно груви и является тем стандартом, который им нужен в работе, а использование Go как раз близко к нулю (кажется одну утилиту написали за много лет). Причем автор, что характерно, тоже тут лукавит: Go значит стандарт, а если на диаграмму посмотреть, то там максимум 29% получается таких, кому он интересен. Стандарт, да — едва за четверть перевалил.
В Go еще крутая кросс компиляция. Очень удобно писать код на хосте, а потом, когда хочется запустить его же на целевом устройстве с embedded linux просто меняем параметры GOOS и GOARCH и получаем бинарник, который только скопировать и запустить.

Я когда пошел работать в Google, думал, что прокачаю скил в С++. Но когда увидел, как оно там выглядет, сразу понял, что такой скил никому за пределами гугла не нужен. Это уже не С++, а какой-то свой диалект. Тогда же стало понятно зачем Go придумали, решил попробовать и не жалею.
Вы хотите сказать, что в Google какой-то «свой» диалект С++? Расскажите, интересно.
Не сказал бы, что диалект. Под капотом там самый настоящий современный С++. Но он так густо обмазан какими-то библиотеками, что внешне оно выглядет очень непривычно. Иногда смотришь на обьявление переменной, а там пол странички открывающиеся треугольные скобки, а вторую половину они закрываются. Есть метрика, которая говорит, что readability review в среднем на С++ код занимает 4.5 часа, а на Go код 50 минут. Поучаствовав в некоторых ревью С++ кода, я вижу какие там боль и страдания на ровном месте.

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

Что насчёт библиотек? Я недавно пытался кросс-копировать какую-то дичь под arm. Куча зависимостей: OpenCV, CUDA, ROS (ну да, это с++ библиотеки, чисто для примера). и прочее. Честно, не осилил.

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

А микросервисы как же?

Го действительно прост.
Просто тем что накладывает ограничения, прост как раз тем, что нет миллиона способов сделать одни и те же вещи, тем, что как правило очень мало магии. Магию делать сложно.

Сравнивая с python(откуда девопс в основном и бегут), по опыту места работу выходит так —
Ищу девопсов с python — и нет выбора, кроме как искать людей действительно с богатым опытом, ибо магии у нас вышло много, python активно к этому подталкивает. Новому человеку сложно войти, далеко не все можно отследить по использованию, далеко не всюду вообще понятно что функция получит, часто это какие то аналоги dict-ов. Конечно же это плохо, конечно хочется все переделать. Но это время, и накладывает дополнительные требования к новым работником

С другой стороны — все новые проекты на go. И когда ищу людей — меня совершенно не интересует есть ли у них опыт на го, а только более фундаментальные вещи и реальный опыт с чем нибудь. Ибо никаких сомнений нет — что с любой задачей, по коду(даже когда он не идеален) легко ориентироваться, и новый человек используя нормальный IDE ничего нигде не упустит. Так как все на ладони.

Тут конечно же можно возразить — это преимущество сильно типизированого языка.
Контраргумент тут это вся суть — после безуспешного поиска в течении долгих месяцев человека, который достаточно силен чтоб взять проект на rust-е, при этом имеющие фундаментальные знания(например, банально, понимающий что http request это не только json получаемый через request.body) я решил просто отказаться от rust-а(который достался в наследство), все за несколько месяцев переписалось на go и людей искать стало проще. Притом новый человек помогающий переписывать просто проклинал каждый день, когда ему приходилось в имплементации на rust-е пытаться понять что же оно делает.

То что на go будет написано 15 императивными строчками и понятно за 15 секунд, на rust ради красоты мира завернуто в 7 генериков, пару строчек и тройку map-ов и час для понимания, которое включает в себя чтение документации для разных библиотек. Порог вхождения растет в разы, сложность поддержки растет в разы.

В результате, и weakly typed python и strongly typed, generic, functional rust имеют один и тот же ряд недостатков в сравнении с го, по одинаковым причинам

— Отсутствие 100-процентно рабочих IDE (100% рабочее = всегда, в любой ситуации IDE покажет ошибку если используется неправильный тип и можно отследить использование любого символа, позволяя безопасный рефактор), т.е сложный вход, поддержка
— Как следствие предыдущего — любая магия(коднейм для meta programming), а она будет, обязательно становится сложной для понимания при необходимости ее трогать.
— А так как магия, это обычно связующие звенья бизнес-логики — она тоже становится сложной для поддержки
— Более высокие требования для новых работников == сложный рекрутинг
— Высокая когнитивная нагрузка, думаешь не о том
— Сложность экосистемы, deploy, crossplatform
— В процессе разработки думать приходится о том, откуда взялся NoneType, либо почему тип не подходит в длинной связке генериков, вместо того что код должен делать. И воспринимается это сложней

В общем простота и красота языка в целом, в том какого типа задачи приходится решать ежедневно, а не в том, обрабатывается ошибка одной строчкой или 4мя
Как следствие предыдущего — любая магия(коднейм для meta programming), а она будет, обязательно становится сложной для понимания при необходимости ее трогать.

А мы точно с go сравниваем? А то я наслышан про тамошнюю кодогенерацию как решение для всех проблем.


Высокая когнитивная нагрузка, думаешь не о том

Она просто в Go размазывается по меньшей выразительности и, как следствие, большему объёму кода.


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


В процессе разработки думать приходится о том, откуда взялся NoneType, либо почему тип не подходит в длинной связке генериков, вместо того что код должен делать. И воспринимается это сложней

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


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

Ну тащем да, для того, чтобы жсоны перекладывать из request.body куда-то ещё, сложность языка действительно не нужна. Но это скучные задачи.


Притом новый человек помогающий переписывать просто проклинал каждый день, когда ему приходилось в имплементации на rust-е пытаться понять что же оно делает.

Ну это от человека зависит, может. Я вот код на go видел очень разный от разных людей в разных продуктах, и желания с ним работать и резко переквалифицироваться в go-программиста не возникло.

Не настолько там объем кода больше. Скажем, Jpeg декодер на Го 800 строк, из которых с сотню — комментарии, на Хаскеле — 600.
Производительность, по крайней мере у меня, на го в разы выше, чем на плюсах. Нет этого кошмара с библиотеками и бардака с системами сборки, каналы прекрасны, получаются очень удобные конвейеры обработки данных.
Скажем, Jpeg декодер на Го 800 строк, из которых с сотню — комментарии, на Хаскеле — 600.

Кажется, вы мне придумали занятие на ближайшие выходные!


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

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


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

Возможно, имелась в виду производительность человека )

А, и правда, как-то неоднозначно получилось. С такой интерпретацией и спору нет.

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

Имею аналогичный опыт. Только вы немного не на ту мысль отвечаете.
Речь про скорость разработки, а не производительность получившейся программы.

Так, а насчёт декодера — что вы поддерживаете? JPEG — большой формат, есть разные варианты кодирования.

Я смотрел в стандартную библиотеку го. На хаскеле то, что выскочило по первой ссылке. Обычно объем кода по порядку соответствует размеру спецификации.

А, вы не сами писали. Ну тогда не так интересно.

Почему не интересно? Мы обсуждаем реальное программирование или что?

В реальном мире в любой моей программе моего кода — доли процента. На любом языке.

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

Но мне в первую очередь интересно, насколько быстро я напишу эти свои доли процента (и насколько они будут эффективными).


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

Раз статья выскочила в непрочитанных — а можете таки поделиться ссылкой на то, что реализовано в go? Я погуглил, и нашёл, что это модуль image/jpeg, но его исходники найти не получилось — в организации golang на гитхабе есть репа image, но там про jpeg ничего нет, а в документации нет ссылок на исходники.


Есть что-то здесь, но это сильно не 700 строк, и вообще не факт, что тоже самое. Там в одном файле reader.go больше, а это далеко не весь декодинг.


Я таки ради интереса немного на выходных этим позанимался — за часа четыре (включая чтение спеки) набросал парсер и сериализатор формата JFIF, выглядит примерно так:


тыц
{-# LANGUAGE DeriveGeneric, DeriveAnyClass, RecordWildCards, DuplicateRecordFields, LambdaCase, DerivingVia #-}
{-# LANGUAGE DataKinds, TypeFamilies, GADTs #-}
{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.Image.Jpeg.Decoder.JFIF where

import Prelude hiding(length)

import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Search.DFA as BSLS
import qualified Data.Vector.Unboxed as V
import Data.Binary
import Data.Binary.Get (getRemainingLazyByteString, lookAhead, getLazyByteString)
import Data.Vector.Binary
import GHC.Generics
import GHC.Int

import Data.Binary.MatchBytes
import Data.Binary.Utils

data JfifApp0Thumbnail = JfifApp0Thumbnail
  { xThumbnail :: Word8
  , yThumbnail :: Word8
  , thumbnailData :: V.Vector (Word8, Word8, Word8)
  } deriving (Show)

instance Binary JfifApp0Thumbnail where
  get = do (xThumbnail, yThumbnail) <- get
           let thumbBytes = fromIntegral xThumbnail * fromIntegral yThumbnail * 3
           thumbnailData <- genericGetVectorWith (pure thumbBytes) get
           pure $ JfifApp0Thumbnail { .. }
  put JfifApp0Thumbnail { .. } = put xThumbnail >> put yThumbnail >> put thumbnailData

data DensityUnits = DensityNoUnits | DensityPPI | DensityPPC deriving (Show, Generic, Binary)

data JfifApp0 = JfifApp0
  { length :: Word16
  , identifier :: MatchBytes "jfif app0 identifier" '[ 0x4a, 0x46, 0x49, 0x46, 0x00 ]
  , jfifVersion :: (Word8, Word8)
  , densityUnits :: DensityUnits
  , density :: (Word16, Word16)
  , app0thumbnail :: JfifApp0Thumbnail
  } deriving (Show, Generic, Binary)

data QuantTable = QuantTable
  { length :: Word16
  , tableInfo :: Word8
  , table :: V.Vector Word8
  } deriving (Show)

instance Binary QuantTable where
  get = QuantTable <$> get <*> get <*> genericGetVectorWith (pure 64) get
  put QuantTable { .. } = put length >> put tableInfo >> put table

data SofInfo = SofInfo
  { length :: Word16
  , precision :: Word8
  , imageDims :: (Word16, Word16)
  , components :: CountedBy Word8 (Word8, Word8, Word8)
  } deriving (Show, Generic, Binary)

data HuffmanTable = HuffmanTable
  { length :: Word16
  , tableInfo :: Word8
  , symbolsCounts :: V.Vector Word8
  , symbols :: V.Vector Word8
  } deriving (Show)

instance Binary HuffmanTable where
  get = do length <- get
           tableInfo <- get
           symbolsCounts <- genericGetVectorWith (pure 16) get
           symbols <- genericGetVectorWith (pure $ fromIntegral $ V.sum symbolsCounts) get
           pure HuffmanTable { .. }
  put HuffmanTable { .. } = put length
                         >> put tableInfo
                         >> genericPutVectorWith (const $ put ()) put symbolsCounts
                         >> genericPutVectorWith (const $ put ()) put symbols

data RestartInterval = RestartInterval
  { length :: MatchBytes "restart length" '[ 0x00, 0x04 ]
  , interval :: Word16
  } deriving (Show, Generic, Binary)

newtype RawImageBytes = RawImageBytes { bytes :: V.Vector Word8 }

instance Show RawImageBytes where
  show RawImageBytes { .. } = "<" <> show (V.length bytes) <> " bytes>"

imageDataLength :: BSL.ByteString -> Int64
imageDataLength str = go $ BSL.elemIndices 0xff str
  where
    go [] = strLen
    go (idx : rest) | idx == strLen - 1 = strLen
                    | str `BSL.index` (idx + 1) == 0x00 = go rest
                    | str `BSL.index` (idx + 1) >= 0xd0
                    , str `BSL.index` (idx + 1) <= 0xd7 = go rest
                    | otherwise = idx
    strLen = BSL.length str

unstuff0xff :: BSL.ByteString -> BSL.ByteString
unstuff0xff = BSLS.replace (BS.pack [0xff, 0x00]) (BS.singleton 0xff)

dropRst :: BSL.ByteString -> BSL.ByteString
dropRst str = foldr (\byte -> BSLS.replace (BS.pack [0xff, byte]) BS.empty) str [0xd0 .. 0xd7]

instance Binary RawImageBytes where
  get = do rest <- lookAhead getRemainingLazyByteString
           unstuffed <- dropRst . unstuff0xff <$> getLazyByteString (imageDataLength rest)
           pure $ RawImageBytes $ V.fromListN (fromIntegral $ BSL.length unstuffed) $ BSL.unpack unstuffed
  put RawImageBytes { .. } = V.forM_ bytes $ \case 0xff -> put (0xff :: Word8) >> put (0x00 :: Word8)
                                                   byte -> put byte

data SosImage = SosImage
  { length :: Word16
  , components :: CountedBy Word8 (Word8, Word8)
  , ignorable :: SkipCount Word8 3
  , imageBytes :: RawImageBytes
  } deriving (Show, Generic, Binary)

data RawSegment = RawSegment
  { segmentId :: Word8
  , length :: Word16
  , bytes :: V.Vector Word8
  } deriving (Show)

instance Binary RawSegment where
  get = do segmentId <- get
           len <- get
           RawSegment segmentId len <$> genericGetVectorWith (pure $ fromIntegral len - 2) get
  put RawSegment { .. } = put segmentId >> genericPutVectorWith (const $ put length) put bytes

data JfifSegment
  = App0Segment (MatchBytes "app0 segment" '[ 0xdb ], JfifApp0)
  | DqtSegment  (MatchBytes "dqt segment"  '[ 0xdb ], QuantTable)
  | SofSegment  (MatchBytes "sof segment"  '[ 0xc0 ], SofInfo)
  | DhtSegment  (MatchBytes "dht segment"  '[ 0xc4 ], HuffmanTable)
  | DriSegment  (MatchBytes "dri segment"  '[ 0xdd ], RestartInterval)
  | SosSegment  (MatchBytes "sos segment"  '[ 0xda ], SosImage)
  | UnknownSegment RawSegment
  deriving (Show, Generic)
  deriving Binary via Alternatively JfifSegment

data JpegJFIF = JpegJFIF
  { soi :: MatchBytes "jpeg header" '[ 0xff, 0xd8 ]
  , segments :: Some (MatchBytes "segment header" '[ 0xff ], SkipByte 0xff, JfifSegment)
  , eoi :: MatchBytes "end of image" '[ 0xff, 0xd9 ]
  } deriving (Show, Generic, Binary)

data ParseFailure
  = NotEOF { parsedContents :: JpegJFIF, remainingLength :: Int }
  | ParseError { fmtErrorString :: String }

parseJpegJFIF :: BSL.ByteString -> Either ParseFailure JpegJFIF
parseJpegJFIF str
  | Left (_, _, msg) <- decoded = Left $ ParseError msg
  | Right (remainingBytes, _, res) <- decoded
  , not $ BSL.null remainingBytes = Left $ NotEOF res $ fromIntegral $ BSL.length remainingBytes
  | Right (_, _, res) <- decoded = Right res
  where
    decoded = decodeOrFail str

encodeJpegJFIF :: JpegJFIF -> BSL.ByteString
encodeJpegJFIF = encode

За вычетом импортов меньше 150 строк.


Опять же, это работа с форматом, поэтому полного декодера со всеми Хаффманами и преобразованиями Фурье тут нет, это я на следующих вечерах и выходных буду делать (но код, понятное дело, от этого сильно не вырастет). Большая часть кода по сериализации/десериализации генерируется компилятором на базе объявлений соответствующих типов через инструкции в библиотеке (за это отвечает Generic и Binary в deriving) — то есть, в компиляторе хардкода нет. Часть библиотечных вещей вроде MatchBytes, Some, SkipByte или Alternatively написана вообще мной же вот прям тут — кстати, надо вынести в отдельный пакет, очень реюзабельно получилось. Какая-то нетривиальная логика по (де)сериализации есть только в нескольких местах, где она действительно важна, вроде пропусков restart-маркеров в теле картинки (что и гошная реализация делает), это на уровне типов выражать в хаскеле уже труднее.


Ну и, конечно, нет бесконечной ряби в глазах от if err != nil, которые в коде по ссылке выше встречается 20 раз.


Особо не тестил, может, накосячил где, но несколько картинок у меня на харде оно вполне жрёт.

golang.org/src/image/jpeg/reader.go?s=22312:22357#L767
если в документации
golang.org/pkg/image/jpeg/#Decode
тыкнуться в имя функции, откроется исходник.
Но это то же, что вы нашли.
Там почти всё декодирование, за исключением функции DrawYCbCr golang.org/src/image/internal/imageutil/impl.go?s=484:578#L6
decode читает последовательность байтов и выдаёт декодированный двумерный массив пикселов.
Хаскелевский код в
hackage.haskell.org/package/jpeg-0.0.1.1/jpeg-0.0.1.1.tar.gz
выглядит более императивным.
В принципе, если и в гошном коде добавить что-то подобное MatchBytes, if-ов можно сильно убавить.
В гошной реализации явно не хватает микроскопического DSL, типа той же функции сравнения с образцом, но даже и в языке с зависимыми типами десятки вариантов зависимостей форматов структур от данных в битовых полях нагребут сравнимый объём текста. Но выглядеть всё будет покрасивше, да.
Но это скучные задачи.
Но ведь их тоже нужно делать. Более того, на практике их нужно делать чаще, чем какие-нибудь нескучные задачи.

А пусть их кто-нибудь другой делает.


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

— Отсутствие 100-процентно рабочих IDE (100% рабочее = всегда, в любой ситуации IDE покажет ошибку если используется неправильный тип и можно отследить использование любого символа, позволяя безопасный рефактор), т.е сложный вход, поддержка

Не знаю, что там с IDE, но rust-analyzer (относительно rls прошлого поколения) уже способен справиться с большинством типов. Но, увы, все еще может споткнуться о что-нибудь, да.

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

Это было что-то невероятное.

1. Основы языка освоил буквально за несколько часов (есть опыт работы с С, JS, Python и жуткий опыт написания многопоточных штук на pthreads). Сейчас добиваю тонкости с помощью ютуба по вечерам дома.
2. В языке нет магии! Код максимально понятен и корректен. Достаточно взгляда и ты сразу понимаешь что тут происходит. Да он становится более монструозный, но в итоге в голове приходится держать меньше абстракций.
3. Приложение ест какие-то смешные цифры по ресурсам. В интернете часто жалуются на жрущую память, это из-за не понимания работы каналов и короутин. Часто там допускают ошибки из-за чего происходит утечка. К тому же сам Go резервирует заранее какой-то объем ОЗУ, чтоб повторно не запрашивать у ОС.
4. Руководство довольно и в перспективе планируем дальше отпиливать от монолита ресурсоемкие куски.

Первые критичные минусы с которыми столкнулся, это ограниченность некоторых сторонних библиотек. Они как правило стабильны и быстрые, но с ограниченным функционалом.
Второе это логгер. Не понятно как сделать всё действительно несвязанно для удобных тестов, при этом чтоб везде был свой логер. Как я понял тут либо глобальная переменная, либо везде пихать логер отдельным атрибутом. Или как-то можно сделать через init пакета?
Может кто-то более опытный поделится опытом best practice?
НЛО прилетело и опубликовало эту надпись здесь
Уже к концу проекта понял, что что-то сложнее микросервиса, делающего что-то одно, или прототипа на Go лучше не писать (а писали мы бэкенд и несколько служебных микросервисов).


Да, я это понимаю. Отношусь к языкам как к инструментам.
Стараемся чередовать и использовать в тех сферах где они сильны.

А сервис это инфраструктурный или бизнес? Мне вот как-то на Go совсем не зашло предметную область описывать в финансах.


В языке нет магии!

Ну как сказать, когда результат работы функции зависит от того, с буквы какого регистра начинается свойство её параметра… Да и конструкции типа Page int \json:"page"`` совсем не очевидно как работают

Инфраструктурный, но бизнес страдает в первую очередь. Потому и переписываем.
Ну как сказать, когда результат работы функции зависит от того, с буквы какого регистра начинается свойство её параметра… Да и конструкции типа Page int \json:«page»`` совсем не очевидно как работают

Есть такое, но я это к магии не отношу. Скорее особенность синтаксиса которую нужно знать.
Магия это когда есть несколько способов реализации алгоритма, часто для того чтоб сократить написание нескольких строк в одну. К сожалению некоторые злоупотребляют этим и код превращается в сплошную магию.

Я к тому, что у меня впечатление, что Go неплох для задач типа "перекладывать json из http в amqp и обратно", но плох для "определение кредитного лимита на основании анкеты заемщика" или "учёт материальных активов"

Я бы для этих предметных областей и Яву бы не использовал. Минимальное требование — перегружаемые операторы для своих типов (Money и подобное), иначе более-менее сложные формулы становятся нечитаемыми, как будто это налоговый кодекс.

плох для «определение кредитного лимита на основании анкеты заемщика» или «учёт материальных активов»

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

Хотя и на go можно написать в виде отдельного сервиса для расчета определенных задач.
Писать монолит или что-то типа ERP я бы не стал.

Даже взять работу с базой. В go нелься написать (прошу поправить, может существуют решения):
select * from foo;

Нужно обязательно перечислить поля после select и описать структуру.

Может есть способ вернуть результат в виде той же мапы?
Можно, go это язык программирования, не вижу проблемы написать любой функционал, и даже без дженериков и магии, стоит только включить мозг а не скриптовать чужими библиотеками
а не скриптовать чужими библиотеками

Ok, буду писать свой драйвер бд…
Можно и так :)
Я имел ввиду что одно дело тупо не задумываясь, пользоваться чужими разработками, и грешить на язык что в нём нет какого то функционала… Язык программирования он на то и язык программирования чтобы на нём программы писать, и необходимый функционал.
Для постгресс есть хорошая библиотека github.com/jackc/pgconn имеет достаточный функционал чтобы получить имена и типы возвращаемых запросом полей, затем можно построить map и заполнить её данными…

По-моему, чем больше язык позволяет не задумываться и делать вещи тупо, тем лучше.

думаю с таким подходом не программистом лучше быть…

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

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

У вас строго структурированные данные в базе, зачем вы мапами мараетесь?

Задача может быть написать "GoMyAdmin" )

Странно, у нас, видимо, какие-то неправильные девопсы. В основном баш и груви.

С ужасом и даже отвращением вспоминаю место, где писали многостраничные скрипты на баше.
Что касается груви, то он мне нравится сам по себе, но обычно его используют в дженкинсе, и размазывают код по его пайплайнам, в лучшем случае — библиотекам пайплайнов.
Все это очень неподдерживаемо, непроизводительный труд, все имеет статус legacy прямо при написании… Я действительно рекомендую посмотреть в сторону python или go.

Да, я про пайплайны дженкинса. Ну еще скрипты папета. Это то что приходилось делать мне когда были сисадмины, а девопсы не существовали как класс. Я не представляю, что еще можно автоматизировать с помощью ЯП.
С точки зрения поддержки и переносимости оно действительно все ужасно. Большим шагом вперед было когда пайпланы стали храниться в репе. Но все равно это далеко до либ менеджмента как с мавеном или грэдлом. Не знаю как переход на питон или го тут может сильно помочь, (но я не девопс, отстал от темы)

Я не представляю, что еще можно автоматизировать с помощью ЯП.

Сложные выгрузки и/или конвертации данных, работу с АПИ провайдеров типа облачных для создания и связи ресурсов, триггерные события типа получения смс или звонка по телефону на модем и их обработка

Ансибл и груви. И питон.
С большим воодушевлением осваивал и писал первый код на Go. Пожалуй, один их луших языков для всяких системных или сетевых штучек типа Кубера или DNS-сервера.

К сожалению, писать бизнес-логику или работу с БД на нем — такое себе удовольствие. Использование интерфейсов вместо полноценного ООП, отсутствие гибкой системы типов, назойливая проверка ошибок по всему коду вместо элегантных исключений… выматывает быстро.

Что касается скорости для типичных сайтов, CRUD или REST API — на данный момент все языки имеют высокопроизводительные эффективные фреймворки под эти задачи.

Я сейчас активно использую Comet [1], получается более короткий, быстрый и надежный код (сравниваю с аналогичными по функционалу аппами, которые недавно писал на Golang), независимая экспертиза в тестах Techempower [2] подтверждает.

[1] github.com/gotzmann/comet
[2] www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=query
Я сейчас активно использую Comet [1], получается более короткий, быстрый и надежный код (сравниваю с аналогичными по функционалу аппами, которые недавно писал на Golang), независимая экспертиза в тестах Techempower [2] подтверждает.

Впечатляет. Это на php8 с jit?
Нет, это PHP7 + libevent. На PHP8/JIT в теории будет быстрее, но пока еще не тестировал.

Простите, но не хватает объяснения, почему вы считаете это поведение неожиданным. Если знать, что массив — сущность мутабельная, но append создаёт новый массив — всё совершенно логично и именно так, как ожидается. Или вы пришли из php?

Считаю неожиданным, потому-что каждый кейс родился из ошибок, допущенных разными программистами в реальных проектах. Сам я начинал с C++ с управлением выделением памяти в ручном режиме.
А не могли бы вы привести пример того, что можно в этом примере ожидать и как оно в приниципе могло бы работать по-другому?
Потому что я вот в принципе не могу представить себе другого варианта, при котором имело бы смысл писать a = append(a, 5).
Потому что если мы вызываем append, передавая туда слайс (а не адрес слайса), то стало быть, он уходит туда по значению (иначе было бы неэффективно). А раз результат нужно куда-то присвоить, а не просто сделать append(a, 5), то стало быть, append порождает новый стайс, а не меняет старый.
Отсюда все четыре примера однозначно следуют.
Или возможна какая-то другая логика?

А попробуйте забить на эффективность — может и другая логика обозначится

А зачем? По-умолчанию нужно писать эффективный код и API должны быть эффективными.

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

Ещё может быть, что не код и API должны быть эффективными, а программист

Бывает да. И как я уже писал: я с большим уважением отношусь к разработчикам, которые могут, без того, чтобы сойти с ума, “быть эффекивными”.

Может я просто в каких-то “неправильных” компаниях с подобными “эффекивными программистами” сталкивался, но… я не могу вспомнить случаев, когда эта эффективность вела бы к чему-то полезному.

То есть да, “эффекивные программисты” порождают какое-то космическое количество кода, стендапы и митапы, KPI зашкаливают, менеджеры в восторге… выход полезного продукта — околонулевой.

Что-то в духе “старого” и “нового” Skype.

И я говорю даже не о том, что “старый” Skype жрал ресурсов на порядок (если не два) меньше, чем “новый”.

А просто о том, что при наличии явно не меньшего количества разработчиков и использовании самых новейших технологий… полезные (для пользователя) фичи там появляются реже, чем во времена, когда Skype только появился и использовал “устаревшие подходы”.

В конце-концов эффективность программиста в коммерческой организации определяется тем, сколько его код приносит или экономит денег компании или "енэйблид" возможность другим это делать.

Не льстите себе. “Эффективность программиста” определяется тем, сколько его менеджер сможет нарисовать красивых графиков.

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

Даже если второе эффективнее с точки зрения зарабатывания денег.
Без понимания того как оно внутри устроено совершенно не очевидные примеры.
Разные выводы в этих тоже очевидны?
play.golang.org/p/hnd2C9Gva4s
play.golang.org/p/w7nAvEMdjIt

Разница в инициализации слайса приводит к разному поведению. Если хорошо знаешь go то да, очевидны, но в том то и смысл, что с виду просые вещи подкидывют сюрпризы.
Без понимания того как оно внутри устроено совершенно не очевидные примеры.
Конечно. Но в этом-то и фокус.

У меня есть ощущение, что вы с разработчиками Go находитесь по разные стороны фундаментальной “дилеммы Хоара”. Который как-то заметил, что в программировании есть два строго противоположных подхода:
  • Первый: написать код настолько просто, что в нём очевидно не будет проблем.
  • Второй: сделать код настолько сложным, чтобы в нём нельзя было найти очевидных проблем.


Разработчики Go, очевидно, исповедуют первый подход. И они даже подробно описали как и почему они сделали слайсы такими, какие они есть.

И если вы держите в голове вот именно ту (весьма простую и достаточно эффективную) конструкцию, которая стоит за слайсами, то разобраться в приведённых вами примерах несложно. Фактически слайсы являются банальным обобщением тривиального std::spanа (возможным за счёт использования GC).

Если хорошо знаешь go то да, очевидны, но в том то и смысл, что с виду просые вещи подкидывют сюрпризы.
Потому что они только “с виду просты”. Любые операции со строками нефиксированной длины — сложны. Об этом Джоел писал много лет назад.

И соотвественно, вы можете выбрать один из двух подходов:
  • Первый: реализовать всё просто и эффективно — но тогда нужно будет понимать как вы это сделали.
  • Второй: когда, попробовав сделать из дерьма конфетку, путём заворачивания дерьма в дерьмо и посыпая его дополнительным слоем дерьмы вы получаете на выхода, всё равно, дерьмо.


В принципе я с большим уважением отношусь к разработчикам на PHP или JavaScript. Ну потому что лично я бы никогда не смог плавать в этих бесконечных горах дерьма. Меня бы стошнило. А им — нормально, даже какое-то удовольствие, вроде, от порождения Эверестов из дерьма там, где можно было бы всё сделать гораздо проще, получают.

Да, собственно, никаким другим способом современные “программные шедевры” создать и невозможно. Ибо они создаются по принципу “что? после 135 переносов унитаза, раковины и телевизора по всему 100 этажному небоскрёбу у вас смешалось в кучу содержимое водопровода и канализации, а ещё оно выделяет газы, так как проводка искрит… ну сделайте что-нибудь, мне же это завтра заказчику показывать!”.

Если вам нужно именно это, то да, Go для вас не подходит. Он немного о другом.
Разработчики Go, очевидно, исповедуют первый подход.

Очевидно, неудачно. Ведь на го пишется код настолько простой, что в нём очевидно куча проблем.

Ну почему же. Вы же сами сказали, что никаких сюрпризов не увидели. Так с самим Go всё отлично.

Уж всяко лучше, чем на PHP, где масса конструкций выдающих разный результат в зависимости от версии PHP.

А уж что там пишут разработчики на Go, используя этот язык — напрмую к качествам языка отношения не имеет.

P.S. Кстати стоит заметить, что JavaScript, при всех его недостатках, последователен. Все версии ведут себя одинаково. Не всегда логично, но хот бы одинаково. Это всё-таки не PHP…
Ну почему же. Вы же сами сказали, что никаких сюрпризов не увидели. Так с самим Go всё отлично.

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


Хотя потом я понял, что человека может удивлять, ведь в Гоу ты значения по умолчанию передаёшь по значению, а в данном случае хотя не указана передача по ссылке — слайс передаётся по ссылке.

а в данном случае хотя не указана передача по ссылке — слайс передаётся по ссылке.
Слайс как раз не передаётся по ссылке. Посмотрите на примеры из этого комментария.

Слайс является ссылкой — в этом фишка. А передаётся он как раз по значению.

Но в целом, год на Гоу код простой и плохой.
Плохой-то почему, вдруг? То, что просто — на Go и выглядит просто. То, что сложно — выглядит сложно.

Я бы сказал, что это развитие идей C (не C++!).

К этому можно относиться по разному, но вижу почему это, вдруг, обязательно прям плохо.
Плохой-то почему, вдруг? То, что просто — на Go и выглядит просто. То, что сложно — выглядит сложно.

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

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

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

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

ну скорее как раз наоборот. Хреновая типизация в го ведёт к повышенному количеству багов. Именно потому "решить небольшую задачу и забыть о ней" для гоу подходит. Нету длительного суппорта — меньше увеличение сложности кода — меньше лавины багов.

Горутины и Каналы — фичи, которые хвалят даже те, кто невзлюбил Go.

Возьмите себе немного Kotlin.


Ну а вообще мысль, что многопоточность может быть "простой" — это опасная иллюзия.

На каналах тоже достаточно просто получается
func main() {
	deers := make(chan chan struct{})
	elves := make(chan chan struct{})
	for i := 1; i < 10; i++ {
		go helper("Reindeer "+strconv.Itoa(i)+" delivering toys", deers)
		go helper("Elf "+strconv.Itoa(i)+" meeting in the study", elves)
	}
	sleigh := make([]chan struct{}, 0)
	shop := make([]chan struct{}, 0)
	for {
		select {
		case d := <-deers:
			sleigh = append(sleigh, d)
		case e := <-elves:
			shop = append(shop, e)
		}
		if len(sleigh) == 9 {
			fmt.Printf("Ho! Ho! Ho! let’s deliver toys\n")
			for _, c := range sleigh {
				c <- struct{}{}
			}
			sleigh = sleigh[:0]
			time.Sleep(200 * time.Millisecond)
		} else if len(shop) == 3 {
			fmt.Printf("Ho! Ho! Ho! let’s meet in my study\n")
			for _, c := range shop {
				c <- struct{}{}
			}
			shop = shop[:0]
			time.Sleep(200 * time.Millisecond)
		}
	}
}

func helper(name string, ready chan chan struct{}) {
	work := make(chan struct{})
	for {
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
		ready <- work
		_ = <-work
		fmt.Println(name)
	}
}

Я поглядел на этот код минут пять и, если честно, не очень понял, для чего именно и как тут используется многопоточность.

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

Любой код с STM можно переписать на атомиках, мьютексах или тому подобном. Смысл STM в том, что о коде в соответствующих терминах очень просто рассуждать, а ошибки делать очень сложно.

Канал каналов, серьёзно?

С горутинами проблема примерно одна — управление временем жизни. И, в принципе, каналами она тоже решается, нужно только руку набить.
С горутинами проблема примерно одна — управление временем жизни

Можно рассказать поподробнее?

Да что рассказывать. Для запуска горутины достаточно записать go, это просто. Что не так просто — это сказать, где и сколько у вас запущено горутин, чем они занимаются, и когда закончат.


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


Но это несколько больше кода, чем тупо go, и его надо везде копипастить, потому что заабстрагировать не очень получится.

то же самое, что и с объектами, скажем, в плюсах. надо отслеживать, кто от них зависит и удалять, когда они уже никому не нужны. утечка потоков — очень распространённая ошибка.
Для меня большая боль это то, что http.Request мутабельная структура и просто передаётся по цепочке вызовов. Люди ловят race conditions в стандартной библиотеке. Пример: github.com/golang/go/issues/30597

Пишу умный компонент кэша для прокси-сервера Caddy. Работа с http/2 это ещё то страдание. Наш тикет в репе Caddy, который вывел на баг в стандартной библиотеке: github.com/caddyserver/caddy/issues/4038
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.