Сравнение одинакового проекта в Rust, Haskell, C++, Python, Scala и OCaml

Автор оригинала: Tristan Hume
  • Перевод
В последнем семестре университета я выбрал курс компиляторов CS444. Там каждая группа из 1-3 человек должна была написать компилятор из существенного подмножества Java в x86. Язык на выбор группы. Это была редкая возможность сравнить реализации больших программ одинаковой функциональности, написанных очень компетентными программистами на разных языках, и сравнить разницу в дизайне и выборе языка. Такое сравнение породило массу интересных мыслей. Редко можно встретить такое контролируемое сравнение языков. Оно не идеально, но намного лучше, чем большинство субъективных историй, на которых основано мнение людей о языках программирования.

Мы сделали наш компилятор на Rust, и сначала я сравнил его с проектом команды на Haskell. Я ожидал, что их программа будет намного короче, но она оказалась того же размера или больше. То же самое для OCaml. Затем сравнил с компилятором на C++, и там вполне ожидаемо компилятор был примерно на 30% больше, в основном, из-за заголовков, отсутствия типов sum и сопоставлений с образцом. Следующее сравнение было с моей подругой, которая сделала компилятор самостоятельно на Python и использовала менее половины кода, по сравнению с нами, из-за мощности метапрограммирования и динамических типов. У другого товарища программа на Scala тоже была меньше нашей. Больше всего меня удивило сравнение с другой командой, которая тоже использовала Rust, но у них оказалось в три раза больше кода из-за разных дизайнерских решений. В конце концов, самая большая разница в количестве кода оказалась в пределах одного языка!

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

Содержание


  • Почему я считаю это содержательным
  • Rust (основа для сравнения)
  • Haskell: 1,0-1,6 размера в зависимости от того, как считать, по интересным причинам
  • C++: 1,4 размера по очевидным причинам
  • Python: 0,5 размера из-за причудливого метапрограммирования!
  • Rust (другая группа): трёхкратный размер из-за иного дизайна!
  • Scala: 0,7 размера
  • OCaml: 1,0-1,6 размера в зависимости от того, как считать, похоже на Haskell

Почему я считаю это содержательным


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

  • Никто (включая меня) не знал, что я буду измерять этот параметр, поэтому никто не пытался играть в метрики, все просто пытались закончить проект быстро и правильно.
  • Все (за исключением проекта Python, о котором я расскажу позже) реализовывали программу с единственной целью пройти один и тот же автоматизированный набор тестов в одни и те же сроки, поэтому результаты не могут быть сильно искажены группами, решающими разные проблемы.
  • Проект был выполнен в течение нескольких месяцев, с командой, и должен был постепенно расширяться и проходить как известные, так и неизвестные тесты. Это означает, что было полезно писать чистый понятный код.
  • Кроме прохождения тестов курса, код не будет использоваться ни для чего другого, никто не будет его читать и, будучи компилятором для ограниченного подмножества Java в текстовый ассемблер, он не будет полезен.
  • Никакие библиотеки, кроме стандартной библиотеки, не разрешены, и никакие хелперы для парсинга, даже если они есть в стандартной библиотеке. Это означает, что сравнение не может быть искажено мощными библиотеками компилятора, которые есть только у некоторых команд.
  • Были не только публичные, но и секретные тесты. Они запускались один раз после окончательной сдачи. Это означало, что был стимул написать свой собственный тестовый код и убедиться, что компилятор надёжный, правильный и обрабатывает сложные пограничные ситуации.
  • Хотя все участники — студенты, но я считаю их вполне компетентными программистами. Каждый по крайней мере два года проходил стажировку, в основном, в высокотехнологичных компаниях, иногда даже работал над компиляторами. Почти все они программируют в течение 7-13 лет и являются энтузиастами, которые много читают в интернете за рамками своих курсов.
  • Сгенерированный код не учитывался, но учитывались файлы грамматики и код, который генерировал другой код.

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

Rust (основа для сравнения)


Я и один из моих товарищей раньше каждый написал более 10k строк на Rust, а третий коллега написал, возможно, 500 строк на каких-то хакатонах. Наш компилятор вышел в 6806 строк wc -l, 5900 строк исходника (без пробелов и комментариев), и 220 КБ wc -c.

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

Я написал другую статью с описанием нашего дизайна, который прошёл все публичные и секретные тесты. Он также содержит несколько дополнительных функций, которые мы сделали для удовольствия, а не для прохождения тестов, что, вероятно, добавило около 400 строк. Также в нём около 500 строк наших модульных тестов.

Haskell


В команду Haskell входило два моих друга, которые написали, возможно, пару тысяч строк Haskell каждый, плюс прочитали много онлайн-контента о Haskell и других подобных функциональных языках, таких как OCaml и Lean. У них был ещё один товарищ по команде, которого я не очень хорошо знал, но, похоже, сильный программист и раньше использовал Haskell.

Их компилятор составил 9750 строк по wc -l, 357 КБ и 7777 строк кода (SLOC). У этой команды также единственные существенные различия между этими соотношениями: их компилятор в 1,4 раза больше нашего по строкам, в 1,3 раза по SLOC и в 1,6 раза по байтам. Они не реализовали никаких дополнительных функций, прошли 100% публичных и секретных тестов.

Важно отметить, что включение тестов больше всего сказалось именно на этой команде. Поскольку они тщательно подошли к правильности кода, то включили 1600 строк тестов. Они поймали несколько пограничных ситуаций, которые наша команда не отловила, но просто эти случаи не проверялись тестами курса. Так что без тестов с обеих сторон (6,3 тыс. строк против 8,1 тыс. строк) их компилятор всего на 30% больше нашего.

Здесь я склоняюсь к байтам как более разумной мере сравнения объёма, потому что в проекте Haskell в среднем более длинные строки, поскольку у него нет большого количество строк из одной закрывающей скобки, и у них rustfmt не разбивает однострочные цепочки функций на несколько строк.

Покопавшись с одним из моих друзей по команде, мы придумали следующее объяснение этой разнице:

  • Мы использовали рукописный лексический анализатор и метод рекурсивного спуска, а они — генератор на NFA и DFA и LR-парсер, а затем проход для преобразования дерева синтаксического анализа в AST (абстрактное синтаксическое дерево, более удобное представление кода). Это дало им существенно больше кода: 2677 строк по сравнению с нашим 1705, примерно на 1000 строк больше.
  • Они использовали причудливый генерик AST, который переходил к различным параметрам типа, поскольку больше информации добавлялось в каждом проходе. Это и больше вспомогательных функций для перезаписи, вероятно, объясняют, почему их код AST примерно на 500 строк больше, чем наша реализация, где мы собираем с литералами struct и мутируем поля Option<_> для добавления информации по мере прохождения.
  • У них ещё около 400 строк кода при генерации, которые в основном связаны с большей абстракцией, необходимой для генерации и объединения кода чисто функциональным способом, где мы просто используем мутацию и запись строк.

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

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

Поклонники Haskell возразят, что та команда, вероятно, не использовала в полной мере возможности Haskell, и если бы они лучше знали язык, то могли бы сделать проект с меньшим количеством кода. Согласен, кто-то вроде Эдварда Кметта может написать тот же компилятор в значительно меньшем объёме. Действительно, команда моего друга не использовала много причудливых суперпродвинутых абстракций и причудливых библиотек комбинаторов, таких как lens. Однако всё это сказывается на читаемости кода. Все люди в команде — опытные программисты, они знали, что Haskell способен на очень причудливые вещи, но решили не использовать их, потому что решили, что на их понимание потребуется больше времени, чем они сэкономят, и сделает код сложнее для понимания другими. Это кажется мне реальным компромиссом, а утверждение, что Haskell волшебно подходит для компиляторов, переходит в нечто вроде «Haskell требует чрезвычайно высокой квалификации для написания компиляторов, если вы не заботитесь о поддержке кода людьми, которые также не очень искусны в Haskell».

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

С++


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

Их проект состоял из 8733 строк и 280 КБ, не включая тестовый код, но включая около 500 строк дополнительных функций. Что делает его в 1,4 раза больше нашего кода без тестов, у которого тоже около 500 строк дополнительных функций. Они прошли 100% публичных тестов, но только 90% секретных тестов. Предположительно потому, что они не реализовали причудливые массивы vtables, требуемые спецификацией, которые занимают, возможно, 50-100 строк кода.

Я не очень глубоко копался в этих различиях по размеру. Предполагаю, это в основном объясняется:

  • Они используют LR-парсер и рерайтер деревьев (tree rewriter) вместо метода рекурсивного спуска.
  • Отсутствие типов sum и сопоставлений с образцом в C++, которые мы широко использовали и которые были очень полезны.
  • Необходимость дублировать все сигнатуры в заголовочных файлах, чего нет в Rust.

Ещё мы сравнивали время компиляции. На моём ноутбуке чистая отладочная сборка нашего компилятора занимает 9,7 с, чистый релиз 12,5 с, а инкрементная отладочная сборка — 3,5 с. У моего друга не было таймингов под рукой для их билда C++ (используя parallel make), но он сказал, что цифры похожие, с оговоркой, что они помещают реализации множества небольших функций в заголовочные файлы, чтобы уменьшить дублирование сигнатур ценой более длительного времени (именно поэтому я не могу измерить чистые накладные расходы на строки в заголовочных файлах).

Python


Мой друг, очень хороший программист, решила сделать проект в одиночку на Python. Она также реализовала больше дополнительных функций (для удовольствия), чем любая другая команда, включая промежуточное представление SSA с распределением регистров и другими оптимизациями. С другой стороны, поскольку она работала в одиночку и реализовывала множество дополнительных функций, она уделяла наименьшее внимание качеству кода, например, бросая недифференцированные исключения для всех ошибок (полагаясь на бэктрейсы для отладки) вместо того, чтобы реализовать типы ошибок и соответствующие сообщения, как у нас.

Её компилятор состоял из 4581 строки и прошёл все публичные и секретные тесты. Она также реализовала больше дополнительных функций, чем любая другая команда, но трудно определить, сколько дополнительного кода это заняло, потому что многие из дополнительных функций были более мощными версиями простых вещей, которые всем нужно было реализовать, таких как свёртывание констант и генерация кода. Дополнительные функции, вероятно, составляют 1000-2000 строк, по крайней мере, поэтому я уверен, что её код по крайней мере вдвое выразительнее нашего.

Одна большая часть этого различия, вероятно, динамическая типизация. Только в нашем ast.rs 500 строк определений типов, и ещё много типов, определённых в других местах компилятора. Мы также всегда ограничены самой системой типов. Например, нам нужна инфраструктура для эргономичного добавления новой информации в AST по мере прохождения и доступа к ней позже. В то время как в Python вы можете просто установить новые поля на узлах AST.

Мощное метапрограммирование также объясняет часть разницы. Например, хотя она использовала LR-парсер вместо метода рекурсивного спуска, в её случае я думаю, что он занял меньше кода, потому что вместо прохода с перезаписью дерева её грамматика LR включала фрагменты кода Python для построения AST, которые генератор мог превратить в функции Python с помощью eval. Часть причины, по которой мы не использовали LR-парсер, заключается в том, что построение AST без перезаписи дерева потребует много церемоний (создание либо файлов Rust, либо процедурных макросов), чтобы связать грамматику с фрагментами кода Rust.

Другой пример мощи метапрограммирования и динамической типизации — 400-строчный файл visit.rs, это в основном повторяющийся шаблонный код, реализующий визитора на куче структур AST. В Python это может быть короткая функция примерно на 10 строк, которая рекурсивно интроспектирует поля узла AST и посещает их (используя атрибут __dict__).

Как поклонник Rust и статически типизированных языков в целом, я склонен отметить, что система типов очень полезна для предотвращения ошибок и для производительности. Необычное метапрограммирование также может затруднить понимание того, как работает код. Однако это сравнение удивило меня тем, что я не ожидал, что разница в количестве кода будет настолько большой. Если разница в целом действительно близка к необходимости писать в два раза больше кода, я все ещё думаю, что Rust подходящий компромисс, но всё-таки в два раза меньше кода — аргумент, и в будущем я склонен что-то сделать на Ruby/Python, если нужно просто быстро что-то построить в одиночку, а затем выбросить.

Rust (другая группа)


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

Их проект состоял из 17 211 необработанных строк, 15k исходных строк и 637 КБ, не включая тестовый код и сгенерированный код. У него не было дополнительных функций, и он прошёл только 4 из 10 секретных тестов и 90% публичных тестов на генерацию кода, потому что у них не хватило времени перед дедлайном для реализации более причудливых частей спецификации. Их программа в три раза больше нашей, написана на том же языке, и с меньшей функциональностью!

Этот результат был действительно удивителен для меня и затмевал все различия между языками, которые я исследовал до сих пор. Поэтому мы сравнили списки размеров файлов wc -l, а также проверили, как каждый из нас реализовал некоторые конкретные вещи, которые вылились в разный размер кода.

Похоже, всё сводится к последовательному принятию различных дизайнерских решений. Например, их фронтенд (лексический анализ, синтаксический анализ, построение AST) занимает 7597 строк против наших 2164. Они использовали лексический анализатор на DFA и синтаксический анализатор LALR(1), но другие группы делали подобные вещи без такого количества кода. Глядя на их weeder-файл, я заметил ряд дизайнерских решений, которые отличались от наших:

  • Они решили использовать полностью типизированное дерево синтаксического анализа вместо стандартного однородного дерева синтаксического анализа на основе строк. Вероятно, это потребовало намного больше определений типов и дополнительного кода преобразования на этапе синтаксического анализа или более сложного генератора синтаксического анализа.
  • Они использовали реализации трейтов tryfrom для преобразования между типами дерева синтаксического анализа и типами AST при проверке их правильности. Это приводит к множеству 10-20-строчных блоков impl. Мы для этого использовали функции, возвращающие типы Result, что генерирует меньше строк, а также немного освобождает нас от структуры типов, упрощая параметризацию и повторное использование. Некоторые вещи, которые для нас были однострочными ветвями match, у них были представляли собой 10-строчные блоки impl.
  • Наши типы структурированы так, чтобы уменьшить копипаст. Например, они использовали отдельные поля is_abstract, is_native и is_static, где код проверки ограничений необходимо было скопировать дважды: один раз для void-типизированных методов и один раз для методов с типом return, с небольшими изменениями. В то время как у нас void был просто специальным типом, и мы придумали таксономию модификаторов с mode и visibility, которые применяли ограничения на уровне типа, а ошибки ограничений были сгенерированы по умолчанию для оператора match, который перевёл наборы модификаторов в mode и visibility.

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

Их часть для генерации кода состоит из 3594 строк, а наша — 1560. Я посмотрел на их код и кажется, что почти вся разница в том, что они выбрали промежуточную структуру данных для инструкций ассемблера, где мы просто использовали форматирование строк для прямого вывода ассемблера. Им пришлось определять типы и функции вывода для всех используемых инструкций и типов операндов. Это также означало, что построение инструкций ассемблера заняло больше кода. Где у нас был оператор форматирования с короткими инструкциями, такими как mov ecx, [edx], им нужен был гигантский оператор rustfmt, разбитый на 6 строк, которые строили инструкцию с кучей промежуточных вложенных типов для операндов, включающих до 6 уровней вложенных скобок. Мы также могли выводить блоки связанных инструкций, таких как преамбула функции, в одном операторе форматирования, где им пришлось указывать полную конструкцию для каждой инструкции.

Наша команда рассматривала возможность использования такой абстракции, как у них. Было проще иметь возможность либо выводить текстовую сборку, либо непосредственно выдавать машинный код, однако это не являлось требованием курса. То же самое можно было сделать с меньшим количеством кода и лучшей производительностью, используя трейт X86Writer с методами вроде push(reg: Register). Мы учитывали также, что это могло бы упростить отладку и тестирование, но мы поняли, что просмотр сгенерированного текстового ассемблера на самом деле легче читать и тестировать с помощью тестирования моментальных снимков, если либерально вставлять комментарии. Но мы (видимо, правильно) предсказали, что это займёт много дополнительного кода, и не было никакой реальной выгоды, учитывая наши реальные потребности, поэтому мы не беспокоились.

Хорошо сравнить это с промежуточным представлением, которое команда C++ использовала в качестве дополнительной функции, что отняло у них всего 500 дополнительных строк. Они использовали очень простую структуру (для простых определений типов и кода построения), которая использовала операции, близкие к тому, что требовала Java. Это означало, что их промежуточное представление было намного меньше (и, следовательно, требовало меньше кода построения), чем результирующий ассемблер, поскольку многие языковые операции, такие как вызовы и приведения, расширялись во многие ассемблерные инструкции. Они также говорят, что это действительно помогло отладке, так как вырезало много мусора и улучшала читаемость. Представление более высокого уровня также позволило сделать некоторые простые оптимизации на их промежуточном представлении. Команда C++ придумала действительно хороший дизайн, который принёс им гораздо больше пользы с гораздо меньшим количеством кода.

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

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

Что действительно заставило меня задуматься — это стоимость абстракции. Абстракции могут облегчить расширение в будущем или защитить от некоторых типов ошибок, но их нужно учитывать с учётом того, что вы можете получить в три раза больше кода для понимания и рефакторинга, в три раза больше возможных мест для ошибок и меньше времени на тестирование и дальнейшую разработку. Наш учебный курс отличался от реального мира: мы точно знали, что никогда не коснёмся кода после разработки, это исключает преимущества упреждающей абстракции. Однако, если мне пришлось бы выбрать, какой компилятор расширить произвольной функцией, которую вы скажете позже, я бы выбрал наш, даже не учитывая своё знакомство с ним. Просто потому что в нём намного меньше кода, который нужно понять, и я мог бы потенциально выбрать лучшую абстракцию для требований (например, промежуточное представление команды C++), когда буду знать конкретные требования.

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

Scala


Я также поговорил с другом, который в предыдущем семестре делал проект на Scala, но проект и тесты были точно такими же. Их компилятор состоял из 4141 строки и ~160 КБ кода, не считая тестов. Они прошли 8 из 10 секретных тестов и 100% открытых тестов и не реализовали никаких дополнительных функций. Таким образом, по сравнению с нашими 5906 строками без дополнительных функций и тестов, их компилятор меньше на 30%.

Одним из факторов дизайна в малого объёма стал другой подход к парсингу. Курс позволял использовать инструмент командной строки для генератора таблиц LR. Его никто не использовал, кроме этой команды. Это избавило их от необходимости реализовывать генератор таблиц LR. Им также удалось избежать написания грамматики LR с помощью 150-строчного скрипта Python, который скрапил веб-страницу грамматики Java, которую они нашли в интернете, и переводил её во входной формат генератора. Им всё ещё нужно было сделать какое-то дерево в Scala, но в целом этап парсинга составил 1073 строк по сравнению с нашими 1443, хотя наш метод градиентного спуска здесь давал преимущество по объёму по сравнению со всеми другими командами.

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

OCaml


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

Их компилятор составлял 10914 строк и 377 КБ, включая небольшое количество тестового кода и никаких дополнительных функций. Они прошли 9/10 секретных тестов и все публичные тесты.

Как и другие группы, похоже, что основная разница в размерах связана с использованием для синтаксического анализа LR-парсера и перезаписи дерева, а также конвейера преобразования regex->NFA->DFA для лексического анализа. Их фронтенд (лексический анализ, синтаксический анализ, конструкция AST) составляет 5548 строк, а наш — 2164, с аналогичными соотношениями для байтов. Они также использовали для своего парсера тестирование с ожиданием, что похоже на наши snapshot-тесты, которые помещают ожидаемый вывод за пределы кода, поэтому их тесты парсера составляли ~600 строк из общего числа, а наши — около 200.

Это оставляет для остальной части компилятора 5366 строк у них (461 строка из которых — интерфейсные файлы с объявлениями типов) и 4642 у нас, разница всего на 15%, если считать их интерфейсные файлы, и практически одинаковый размер, если не считать. Похоже, что, не считая наших дизайнерских решений по парсингу, Rust и OCaml кажутся одинаково выразительными, за исключением того, что OCaml нужны интерфейсные файлы, а Rust — нет.

Заключение


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

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

    0
    В последнем семестре университета я выбрал курс компиляторов CS444. Там каждая группа из 1-3 человек должна была написать компилятор из существенного подмножества Java в x86.

    а можно поподробнее о задаче, какие там точно действия после AST, желательно на русском, может и я на питоне что то накалякаю, сравним. Всё думаю о написании универсального компилятора, с контролем юзера над действиями и типом результата…

    Её компилятор состоял из 4581 строки

    для питона — гигантский проэкт, может там всё таки имеется флуд?
    кстати с eval нечестно, но прелесть питона в том что там за строк 10 пишется свой eval

    Другой пример мощи метапрограммирования и динамической типизации — 400-строчный файл visit.rs, это в основном повторяющийся шаблонный код, реализующий визитора на куче структур AST. В Python это может быть короткая функция примерно на 10 строк, которая рекурсивно интроспектирует поля узла AST и посещает их (используя атрибут __dict__).

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

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

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

      +4

      Это перевод, так что вряд ли вам расскажут подробнее. :)

        0
        вряд ли вам расскажут подробнее

        На странице оригинала указаны контакты автора, так что можно попробовать обратиться напрямую.
        0
        По ссылке на курс компиляторов CS444 можно больше о задании, например:
        The overall project for the course is to write a compiler from Joos 1W, a fairly large subset of Java, to i386 assembly language (the Netwide Assembler dialect).

        Возможно даже можно будет найти открытые тесты.
        +10
        Очень интересно было бы еще увидеть сравнение производительности полученных компиляторов.
          –5
          главное не производительность а качество кода. А так на дворе 21 век так что можно задуматься можно ли сделать компилятор на паралельной платформе например CUDA какой нить.
          –1

          А длины строк не сравнивали? Или, может быть, размер по диагонали, скажем, программа на Python диагональю 52".

            +3

            Т.е. самый оптимальный код в таком случае — "квадрат"?

            0

            Есть же the benchmarks game.

              +4

              Там больше про скорость, тут про дизайн.

              +4

              По моему опыту с хаскелем вещи типа lens, uniplate/geniplate и паттерны типа trees that grow сокращают объём кода минимум на порядок по сравнению с теми же плюсами.


              Жалко, что исходников нет.

                +1
                А на сколько они, по вашему опыту, сокращают время на прочтение?

                PS Я знал что вы придёте!
                  +3

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


                  Другое дело, что уровень абстракции чуть выше, чем, не знаю, у immediately invoked function, detector idiom в плюсах и тому подобных вещах.


                  PS Ну не мог же я пропустить такой тред!

                –7
                Необходимость дублировать все сигнатуры в заголовочных файлах, чего нет в Rust.

                Это неверно. Такой необходимости нет.
                  +2
                  Вот и объективная критика пожаловала. Именно так она выглядит.
                    +4
                    (минус не мой)
                    Необходимости конечно нет, но разбиение на реализацию/объявление заметно улучшает читаемость, и ускоряет компиляцию. Это слишком серьезные преимущества, чтоб игнорировать их в проекте на несколько тысяч строк.
                      +1
                      Необходимости конечно нет, но разбиение на реализацию/объявление заметно улучшает читаемость, и ускоряет компиляцию.

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

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

                      и ускоряет компиляцию.

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

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

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

                        +1
                        Если что, я не то чтобы топлю за Rust. Сам пишу на С++ и люблю его умеренной любовью.

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

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

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

                        современный С++ разделить вообще почти нельзя.

                        А можно пример?

                        Это попросту глупость разоблачённая уже давно.

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

                        Я не то чтобы спец, но предположим у вас в реализации функции не какая-нибудь банальщина, а инстанцирование тяжелых линейных солверов из Eigen, приправленных expression templates. В этом случае раздельная компиляция просто жизненно необходима. И это не то чтобы прям редкий случай, заметная доля проектов с С++ это числодробилки, в которых постоянно возникает подобная ситуация.
                          0
                          Если что, я не то чтобы топлю за Rust. Сам пишу на С++ и люблю его умеренной любовью.

                          Ну ваши претензии не работают в рамках логики автора, да и в моей тоже.

                          А можно пример?

                          Всё что связано с шаблонами. Разделять то же auto не имеет смысла, т.к. типы по-сути неизвестен. Много примеров.

                          Было бы здорово увидеть пример.

                          chromium.googlesource.com/chromium/src/+/lkgr/docs/jumbo.md

                          Я не то чтобы спец, но предположим у вас в реализации функции не какая-нибудь банальщина, а инстанцирование тяжелых линейных солверов из Eigen, приправленных expression templates. В этом случае раздельная компиляция просто жизненно необходима. И это не то чтобы прям редкий случай, заметная доля проектов с С++ это числодробилки, в которых постоянно возникает подобная ситуация.

                          Вам это никак не поможет. Это поможет только в том случае, если вы обернёте шаблонное api в c-api и уже с ним будете работать. Но будет уже не С++. Если же ваша логика будет так же шаблонной и так же использовать какую-то логику из библиотеки в cpp-файле, то это ничего не даст.

                          В современном С++-коде код не разбивается(в силу нежелания этого делать и в силу невозможности этого сделать). Существует тысячи ho-библиотек и тот же stdlib так же является ho-библиотекой.

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

                          Просто для понимания сделайте подобный бенчмарк:

                          $ cat a.cpp 
                          #include <iostream>
                          
                          void a() {
                            std::cout << __PRETTY_FUNCTION__ << std::endl;
                          }
                          $ cat b.cpp 
                          #include <iostream>
                          
                          void b() {
                            std::cout << __PRETTY_FUNCTION__ << std::endl;
                          }
                          $ cat c.cpp 
                          #include <iostream>
                          
                          void c() {
                            std::cout << __PRETTY_FUNCTION__ << std::endl;
                          }
                          $ cat main.cpp 
                          #ifdef HO
                          #include "a.cpp"
                          #include "b.cpp"
                          #include "c.cpp"
                          #else
                          void a(); void b(); void c(); 
                          #endif
                          
                          int main() {
                              a(); b(); c();
                          }
                          
                          $ time g++ a.cpp b.cpp c.cpp main.cpp 
                          
                          real    0m0,466s
                          user    0m0,398s
                          sys     0m0,068s
                          
                          $ time g++  main.cpp -DHO
                          
                          real    0m0,178s
                          user    0m0,152s
                          sys     0m0,026s
                          
                          
                          $ time clang++  main.cpp -DHO 
                          
                          real    0m0,684s
                          user    0m0,589s
                          sys     0m0,046s
                          
                          $ time clang++ a.cpp b.cpp c.cpp main.cpp
                          
                          real    0m1,831s
                          user    0m1,721s
                          sys     0m0,100s


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

                            Ну у меня все началось не с претензий, а с попытки обосновать, почему в production ready c++ коде хорошо разделять объявления и реализацию.

                            chromium.googlesource.com/chromium/src/+/lkgr/docs/jumbo.md

                            Прикольно, почитаю, спасибо.

                            Всё что связано с шаблонами. Разделять то же auto не имеет смысла, т.к. типы по-сути неизвестен. Много примеров.

                            Если вы про auto в сигнатуре, то лично я бы предпочел видеть там полноценный тип/шаблон. Да, не во всех ситуациях это будет красиво, но в большинстве — снимет кучу головной боли при чтении кода.
                            Да, шаблоны вполне себе разбиваются. Не на .h и .cpp, конечно, но на блоки объявления и реализации — вполне.

                            Вам это никак не поможет. Это поможет только в том случае, если вы обернёте шаблонное api в c-api и уже с ним будете работать. Но будет уже не С++.

                            Я про случаи когда
                            «MyFuncs.h»
                            Eigen::VectorXf someHeavyFunction(const Eigen::VectorXf &someArg);

                            «MyFuncs.cpp»
                            
                            Eigen::VectorXf someHeavyFunction(const Eigen::VectorXf &someArg)
                            {
                                // Put some heavy template stuff here
                            }

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

                            Пример интересный, спасибо, но не уверен что все это так же хорошо масштабируется на относительно большие проекты
                              0
                              Если вы про auto в сигнатуре, то лично я бы предпочел видеть там полноценный тип/шаблон. Да, не во всех ситуациях это будет красиво, но в большинстве — снимет кучу головной боли при чтении кода.
                              Да, шаблоны вполне себе разбиваются. Не на .h и .cpp, конечно, но на блоки объявления и реализации — вполне.

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

                              К тому же подобный код ненужно читать. 1min / 1s — вы же не будите руками писать тип? И вам ненужно при чтении знать тип. Задача системы типов сделать таким образом, что-бы вы не смогли написать 1min * 1s;

                              К тому же, выводит типы задача ide. Как и задача компилятора(ide) проверять синтаксическую корректность кода. Ненужно это делать за них. Это пустая трата сил на то, что реализуется автоматикой. Всё что автоматизируется должно быть автоматизировано.

                              Я про случаи когда

                              Ну это просто си-api.

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

                              Ещё раз. Если ваша библиотека ho, то вы никаким образом подобное не сделаете. Если вы сделаете шаблонное api, то его нельзя будет засунуть в cpp. Вы можете сделать только сишное api.

                              Таким образом мы просто сделаете обёртку над шаблонной ho-библиотекой. Удобство сишного api крайне сомнительно в случае какой-то обобщённой логики. А такой логики в современной С++ навалом.

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

                              Я думаю, что хром является «относительно большим проектом». Там всё так же работает. Именно это они и сделали.

                                0
                                «Если вы сделаете шаблонное api, то его нельзя будет засунуть в cpp»
                                Что за бред?
                                Я даже больше скажу, его еще и из dll можно экспортировать, если типы с которыми может быть инстанцирован шаблон, заранее известны) У нас в проекте это активно используется, для шаблонов, которые используются с 2-3-4 типами.
                                «есть ограничения» != «нельзя».
                                  –3
                                  Что за бред?

                                  Бред это ваши рассуждения вызванные вашем же непониманием. Шаблон нельзя никуда эспортировать по определению. Эспортировать куда-то можно инстанс.

                                  У нас в проекте это активно используется, для шаблонов, которые используются с 2-3-4 типами.

                                  Что за бред? Во-первых 2-3-4 типа это не шаблон, у шаблона нету никаких ограничений. Во-вторых эспортируются не шаблоны, а инстансы.

                                  Узнайте разницу между первым и вторым перед тем как кричать «бред».
                                    +3

                                    Ну ещё вспомните, что приватные шаблонные функции можно описывать в cpp без явного их инстанциирования.


                                    Разговор ведь не об этом.

                              0
                              современный С++ разделить вообще почти нельзя.

                              Я думаю, имелись ввиду inline, constexpr и шаблонные функции/классы.

                        –11
                        Затем сравнил с компилятором на C++, и там вполне ожидаемо компилятор был примерно на 30% больше

                        Что это за ангажированные намёки ничем не обоснованные? Это сравнение, либо пропаганда?

                        Rust (основа для сравнения)

                        На каком основании это основа, а не это:

                        Rust (другая группа): трёхкратный размер из-за иного дизайна!


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

                        Я не очень глубоко копался в этих различиях по размеру. Предполагаю, это в основном объясняется:

                        А раньше же было «ожидаемо», а теперь «не копался», «не знаю»? Зачем что-то утверждать, если при этом ничего не знать?

                        Отсутствие типов sum и сопоставлений с образцом в C++, которые мы широко использовали и которые были очень полезны.

                        Никакое «сопоставлений с образцом» не является чем-то необходимым и в 95% случаев сливает более мощной концепции визиторов. В С++ есть исключения и ненужно матчить каждую функцию. К тому же, с чего вдруг в С++ нету сумтипов?

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

                        Необходимость дублировать все сигнатуры в заголовочных файлах, чего нет в Rust.

                        То, что это попросту обман я уже писал. Необходимость что-то там дублировать обусловлена разделением на h/cpp-файлы. Никак не обусловлена С++ и это разделение может не использоваться.

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

                        Какая разница какие функции? К тому же, тут явно видно взаимные противоречия.

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

                        Это просто глупость несусветная. Это мифология, которую была опровергнута уже тысячи раз. Особенно хромом. Уплотнение кода способствует ускорению компиляции т.к. инклюды в С++ тяжелые и компилятор делает ненужную работу каждый раз обрабатывая один и тот же код в каждом cpp-файле.

                        К тому же, уровень владения языком представителей С++-лагеря крайне скудны(судя по тексту). В С++ непросто так появилось ho и причины тому просты — шаблонный код попросту нельзя разделить. Попытки были и все они не имели успеха.

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

                        8733 строк

                        Никак не могут компилироваться секунды. К тому же сравнение скорости компиляции на разном железе меня так же удивляют. А так же меня удивляет отсутствие информации об компиляторе и какой-то конкретики в целом.

                        Опять же. Кода мы не видим. Никаких измерений нет. Есть «одна бабка сказала» и какие-то ничем не обоснованные заявления. Прослеживается явная ничем не обоснованная ангажированность и двойные стандарты.

                          0
                          К тому же, с чего вдруг в С++ нету сумтипов?

                          есть, есть std::variant, которым пользуется примерно никто

                            0
                            есть, есть std::variant, которым пользуется примерно никто

                            Это ничего не значит, да и попросту враньё. На это я могу ответить: есть rust|haskell, которыми пользуется примерно никто.

                            PS Опять табун раст-адептов набежал и начал гадить в карму.
                              0
                              На самом деле, здорово бы было, если вы привели бы пример сумтипов на с++ вместе с компактным примером их использования.
                                +3
                                На cppreference есть вполне адекватные примеры.
                                  0
                                  Спасибо, то что нужно
                                    +5

                                    Напоминаю: Haskell и Rust мозголомные и непонятные, а C++ — простой и очевидный. Смотрите не перепутайте!

                                  0

                                  Спросил у автора (т.к. это был перевод). Вот его ответ:
                                  "As far as I know my friend didn’t use the new visitor feature."
                                  Вообщем там наверно обходились enum + switch + cast/union.

                                    +1
                                     Это ничего не значит, да и попросту враньё

                                    я ищу вот так, находятся тесты из LLVM пополам с игрушечными проектами с нулём звёзд. И в QT нашёл QVariant, который умеет оборачивать variant при наличии, который, в свою очередь, находится в игрушечных проектах пополам с копиями qvariant.h


                                    покажите, как правильно искать

                                      +2

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


                                      Вот что действительно никто не использует — какой-нибудь Mach7.

                                      0

                                      По-моему тут неплохо объясняется, почему это даже близко не оно.

                                        –5
                                        почему это даже близко не оно.

                                        Это попросту враньё. Если кратко — автор узнал, что на С++ можно сделать аналогичный match. Вся его критика была именно в том, что «С++ вариант сложный, а match простой». Сам функционал он нигде не критиков. Нигде «это не оно» он не говорил. Что-то подобное он говорил только про версию на ифах. Да и вся его критика существует в рамках надуманного примера.

                                        Если подробнее — это полная и нелепая чушь. После «else if» можно не читать. Основные тезисы — рядовая раст-пропаганда. Почему пропаганда, всё очень просто:

                                        мы однажды захотим его извлечь, и что более важно, узнать его тип.

                                        Никогда мы такого не захотим. Это попытка автора подменить тему. Подобный код — это именно максимум, на что способен pm и это мусор. Это доказано даже автором статьи, да и это в принципе очевидная вещь.

                                        const bool b) const

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

                                        Уже лучше, но в стандартной библиотеке нет ничего похожего на функцию make_visitor,

                                        Она пишется в 2 строчке и ниже она, даже, есть. И тут пропаганда теряется, пропаганда не сообщает нам о проблемах такого подхода? В этом подходе никаких «лишних» строк нету и всё, автор приплыл. Но нет, автор опять меняет тему. И начинает вещать про if constexpr и то, как это сложно. С чего вдруг? Ответ я дал выше — его задача не разобраться, не отразить реальную действительность.

                                        Мы начали с простой цели: посмотреть на содержимое сигма-типа.

                                        Это абсолютно бесполезная херня. Дело в том, что в С++ есть механизмы, которые нету в rust. Там нету компилтайм-вычислений, нету шаблонов, нету перегрузки функций.

                                        Эта пропаганда нагло врёт. visitor — это базовая концепция С++, концепция перегрузки функции. Для неё ненужен никакой variant/visit. Задача variant/visit не обеспечить pm, как уверяет эта второсортная пропаганда, а попросту сделать динамический диспатч. Т.е. вызвать нужную функцию(в рантайме). Вызовы visit(f, x) == f(x). Всё. Сам pm в С++ реализован именно на базе перегрузки.

                                        Но проблема в том, что во всяких rust перегрузки нету и написать f(x) попросту нельзя и match — это костыль, который позволяет получить нечто подобное.

                                        Близкого знакомства с using-объявлениями с переменным количеством параметров, которые только появились в стандарте C++17.

                                        Меня вот что удивляет, почему раст-пропаганда всегда, в своей нелепой методички, апеллирует к стандарту, которого у неё нет? Если мы не можем использовать «новый стандарт», либо «нестандартный код», то мы не можем использовать rust по той же логике. К чему эти нелепые лозунги?

                                        собираются вместе, у них получается std::visit.

                                        Зачем пропаганда пытается куда-то лезть, если она не понимает ничего в С++?

                                        template <typename F>
                                        typename std::enable_if<!std::is_reference<F>::value, int>::type
                                        foo(F f)
                                        {
                                            // ...
                                        }
                                        

                                        Что это за стиль говна? Что это за enable_if из 11 крестов. Как быстро пропаганда меняет тему. Ещё с 14 крестов это выглядит так:

                                        template<typename F> std::enable_if_t<!std::is_reference_v<F>, int> foo(F f) {
                                          
                                        } 
                                        


                                        Я не говорю о каких-то там концептах, а так же смысле подобного поиска ссылки в F.

                                        Скотт Мейерс, автор книг Effective C++ и Effective Modern C++, тоже высказал подобные мысли

                                        Опять пропаганда врёт. Есть сложность вызванная функционалом, а есть сложность вызванная отсутствием функционала. typename std::enable_if<!std::is_reference::value, int>::type — эта сложность вызванная отсутствием функционала и именно с нею борются адекватные люди.

                                        Проблемы существующие в С++ вызваны только тем, что в С++ мало функционала. Его, конечно, куда большее чем в 99% других языков, но всё равно мало. И тут пропаганда нам врёт. Она показывает проблему и говорит «проблемы из-за „много функционала“», но это не так. Точно так же она врёт и про авторов, выдирая их слова из контекста(хотя даже слов не приводит).

                                    +5
                                    Никакое «сопоставлений с образцом» не является чем-то необходимым и в 95% случаев сливает более мощной концепции визиторов.

                                    Сливает по какому критерию? Что по выразительности, что по числу строк кода оно, наоборот, уделывает.


                                    Как вы на визиторах замачтитесь на значение без того, чтобы городить тонны ифов?

                                      0

                                      анонимным классом с перегруженным operator() :)
                                      производится ли при этом проверка на exhaustiveness, я не понял

                                        +3

                                        Для, э, конструкторов в ML-смысле производится. Для значений вам придется писать ифы, не сможете вы выразить матч на значение, а не только лишь на форму конструктора.

                                          –2
                                          godbolt.org/z/18aRS9

                                          К тому же сами рассуждения не имеют смысла. Единственное что может pm «бесплатно» — это switch. Всё что далее — это непредсказуемая лапша. К тому же почти всё это реализуется по аналогии с visit/variant. Правда никому ненужно.

                                          Описание мапы будет всегда лучше. К тому же С++ может без проблем хоть phf в компилтайте сгенерировать.

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

                                        –4
                                        Сливает по какому критерию? Что по выразительности, что по числу строк кода оно, наоборот, уделывает.

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

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

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

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

                                        И самое интересное, как только будет реальная задача. Обход того же дерева — на нём будет очевидна вся несостоятельность pm. Что нам и сообщает автор. Но даже визиторы на rust будут написаны убого, т.к. нету перегрузки. Это будет тонна лапши на генерик-трейтах.

                                        Как вы на визиторах замачтитесь на значение без того, чтобы городить тонны ифов?

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

                                        тонны ифов?

                                        Я бы в контексте раста об этом вообще не заикался. Как там не обработать ошибку без тонны ифов? Из-за тонны ифов раст-методичка даже nullptr из аллокатора игнорирует. А что, памяти нету — можно падать. Надёжность она такая. Особенно смешно выглядят рассуждения раст-адептов про «как вы можете игнорировать NULL у malloc».

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

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

                                          Ну естественно, если автор пишет, что визитор лучше паттерн-матчинга, то это авторитетное мнение, а если кто-то пишет, что паттерн-матчинг лучше визитора, то это глупые лозунги и растопропаганда.


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


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

                                          В каком месте?


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


                                          balance :: Color -> Tree a -> a -> Tree a -> Tree a
                                          balance B (Node R (Node R t1 e1 t2) e2 t3) e3 t4 = Node R (Node B t1 e1 t2) e2 (Node B t3 e3 t4)
                                          balance B (Node R t1 e1 (Node R t2 e2 t3)) e3 t4 = Node R (Node B t1 e1 t2) e2 (Node B t3 e3 t4)
                                          balance B t1 e1 (Node R (Node R t2 e2 t3) e3 t4) = Node R (Node B t1 e1 t2) e2 (Node B t3 e3 t4)
                                          balance B t1 e1 (Node R t2 e2 (Node R t3 e3 t4)) = Node R (Node B t1 e1 t2) e2 (Node B t3 e3 t4)
                                          balance color left elt right = Node { .. }

                                          Цвет и дерево, если что — банально


                                          data Color = R | B deriving (Eq, Show, Generic, NFData)
                                          
                                          data Tree a
                                            = Empty
                                            | Node
                                              { color :: Color
                                              , left :: Tree a
                                              , elt :: a
                                              , right :: Tree a
                                              }
                                            deriving (Eq, Show, Functor, Generic, NFData)

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


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

                                          Может, она и по рантайм-значению может быть ещё?


                                          И зачем вам в реальной жизни нужна частично специализированная шаблонная перегрузка при написании небиблиотечного кода? У вас в ADT ваших часто есть std::variant<std::vector<T1>, std::vector<T2>>, которые надо обрабатывать похожим образом? Поздравляю, вы хреново спроектировали ваши типы, потому что это значит, что T1 и T2 должны быть ветками одного ADT.


                                          Для перегрузки можно использовать композицию из существующих функций/типов.

                                          Для паттерн-матчинга нельзя?


                                          И как этой композицией выразить «заматчить на тип Foo, если его вариант-член bar имеет значение типа Quux»?


                                          Mach7 это может. Перегрузки функций — нет. Но Mach7 никто не пользуется.


                                          Это ненужно.

                                          Понятно.


                                          Как там, строки на уровень шаблонов уже завезли? Или тоже ненужно?


                                          А что, памяти нету — можно падать. Надёжность она такая. Особенно смешно выглядят рассуждения раст-адептов про «как вы можете игнорировать NULL у malloc».

                                          Про аналогичный пропозал для, кажется, C++23 вы, видимо, не слышали?


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


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

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

                                            –8
                                            Ну естественно, если автор пишет, что визитор лучше паттерн-матчинга, то это авторитетное мнение, а если кто-то пишет, что паттерн-матчинг лучше визитора, то это глупые лозунги и растопропаганда.

                                            У вас методичка течёт. Что-то я не вижу от вас разоблачений автора и обсуждения того, какой же он дурак со своими тезисами. Чего же вы гадите мне, а не гадите автору?

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

                                            Меня не интересует «функциональных языках» существующие только для написания хелвордов, хотя и хелворд вы не осилили на нём написать. Когда я увижу многопоточную версию хелворда?

                                            как балансирование красно-чёрного дерева. Как на визиторах будет выглядеть функция

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

                                            И зачем вам в реальной жизни нужна частично специализированная шаблонная перегрузка при написании небиблиотечного кода? У вас в ADT ваших часто есть std::variant<std::vector, std::vector>, которые надо обрабатывать похожим образом? Поздравляю, вы хреново спроектировали ваши типы, потому что это значит, что T1 и T2 должны быть ветками одного ADT.
                                            Дальше лозунгов что-то будет?

                                            Для паттерн-матчинга нельзя?

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

                                            И как этой композицией выразить «заматчить на тип Foo, если его вариант-член bar имеет значение типа Quux»?

                                            Меня мало волнуют лозунги. Аналогичный код на расте в студию. Нету кода — с лозунгами не ко мне.

                                            Mach7 это может. Перегрузки функций — нет. Но Mach7 никто не пользуется.

                                            Что за нелепые попытки на каждый тезис придумывать новый супер-язык?

                                            Понятно.

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

                                            Как там, строки на уровень шаблонов уже завезли? Или тоже ненужно?

                                            Что за нелепица? Строки на уровне шаблонов были всегда.

                                            Про аналогичный пропозал для, кажется, C++23 вы, видимо, не слышали?

                                            Что из этого нелепого срыва покровов следует?

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

                                            Что это за набор нелепых лозунгов. Как быстро меняются методички. Буквально вчера все плюсовали статьи на тему «нужно проверять», а как случилась «лужа» мы всё забыли и оказывает проверять уже ненужно.

                                            Даже на плюсах нельзя непроверить ошибку. В плюсах исключения.

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

                                            Ну да это очевидны. Вы в теме про ваш хелворд были слиты. Раст-адепты в теме про раст были слиты.

                                              +3

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


                                              Mach7, кстати, не новый супер-язык, а PoC-библиотека за авторством Страуструпа и ещё пары крупных имён, а в C++20 не зря добавляют полноценные строки, а не последовательности char'ов, на уровне шаблонов, и думают, как разрешить выделять память в constexpr-контекстах, что ей можно было пользоваться в рантайме, но то такое.

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

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

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

                                                Mach7, кстати, не новый супер-язык

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

                                                а в C++20 не зря добавляют полноценные строки

                                                Почему адепты пытаются мне с умным видом ретранслировать какую-то ахинею услышанную в интеренете?
                                                , а не последовательности char'ов, на уровне шаблонов

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

                                                К тому же то, что там добавляют не имеет никакого отношения к строкам. Та методичка на тему «около С++-лозунгов для начальной школы» работает только в начальной школе, а ну и на хабре.

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

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

                                                  +2
                                                  Адепт поплыл. Вначале методичка была про «нету», а теперь вдруг после гугления оказалось, что есть.

                                                  Как же оно "есть", когда его только добавляют в ещё не вышедший стандарт?

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

                                                    Зачем вы что-то пишите, при этом нихрена не понимая? «строки в шаблонах» есть в С++ с момента появления шаблонов. Причём тут то, что выходит?

                                                    К тому же, методичка про стандарт не работает. Если того, чего нету в «вышедшем стандарте» — нету, то тогда нету раста. Что-то я не вижу от ботов рассуждений про «раста нету»? Чего же так?

                                                    Ну нести ахинею — это стандартное состояние бота.

                                              0
                                              > Как на визиторах будет выглядеть функция
                                              Эту функцию крайне сложно написать хуже на любом языке и с любой техникой. И судя по исходнику типа внизу Вы и сами это заметили ;)
                                                +2

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

                                          +5

                                          Хех, а вот и адепты церкви свидетелей крестов подъехали.

                                            +2
                                            То, что это попросту обман я уже писал. Необходимость что-то там дублировать обусловлена разделением на h/cpp-файлы. Никак не обусловлена С++ и это разделение может не использоваться.

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

                                              +1

                                              Ну в с++ до сих пор нету модулей. Всё через макросы.

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

                                                В С++ нету так же необходимости, а есть возможность.

                                                а в C++ почему-то вот все так делают. Наверное, есть необходимость..

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

                                                technic93 В С++ никакие модули(это просто нелепый лозунг внешней пропаганды) ненужны, а макросы не имеют никакого отношения к модулям, либо теме.
                                                  +1

                                                  Я не совсем понял — Вы за что топите — что модули в С++ не нужны? Или они нужны, но разработчики стандартов их не осилили?

                                                    –1
                                                    Разделение кода не имеет абсолютно никакого смысла, его ноль

                                                    Но почему в таком случае во всех известных мне проектах на плюсах код разделен?

                                                      –3
                                                      Но почему в таком случае во всех известных мне проектах на плюсах код разделен?

                                                      Особенно в бусте и stdlib? Мне попросту лень отвечать на очередную идиотию в интернете. Эти нелепые аргументы уровня начальной школы уровня «мне»? С чего вдруг все проекты стали ограничиваться «мне»?

                                                      К тому же, даже если предположить, что их нет — это ничего не значит. Это такой же аргумент уровня начальной школы. Аргумент уровня «но почему в таком случае во всех известных мне лифтах было нассано?».

                                                      Что там вам известно — ни на что не влияет. Вы либо аргументируете за «нужно», либо продолжаете писать ахинею. Адепты всяких мастей вас будут плюсовать итак. Можете хоть рандомный набор слов писать.

                                                      +2
                                                      В С++ нету так же необходимости, а есть возможность.
                                                      В этих ваших крестах компиялтор тупо не соберёт код, если функция/переменная используется выше объявления. В 1980-ых такой подход может и имел смысл, но не в 2019.
                                                        –2
                                                        В этих ваших крестах компиялтор тупо не соберёт код, если функция/переменная используется выше объявления.

                                                        Меня не перестают удивлять эти срывы покровов. Объявления не имеют никакого отношения к инклюдам.
                                                        +1

                                                        Так модули в С++20 обещают добавить а многие компиляторы уже имеют какую-то реализацию. Сайты clang/gcc и статьи за авторством Страуструппа это не внешняя пропоганда. И речь идёт об одной простой вещи заменить #include по сути макрос на что то лучшее. Сборки больших проэктов занимают время, и поэтому их разделяют на несколько файлов которые компилируются раздельно и паралельно. Без объявления публичного интерфейса в хедере как у вас так получится? Неужили Вcе ваши проэкты инклюдится в один cpp?

                                                      0
                                                      8733 строк
                                                      Никак не могут компилироваться секунды.
                                                      Хорошо что хоть не полчаса собирался.
                                                        0

                                                        Не, ну… Это в релизе собирается минуты четыре, например. Хотя меньше 200 строк.

                                                          0
                                                          У хаскеля свои недостатки. Собирает не быстро, зато весьма многословно и в большие бинарники. Меня, при небольшом знании хаскеля, больше всего удивляет почему нельзя в одном пространстве имён объявить два типа с одинаковыми именами полей.
                                                            +1
                                                            Собирает не быстро, зато весьма многословно и в большие бинарники.

                                                            Зависит от вашего кода. Код с type families и прочей ерундой на уровне типов, да, медленно собирается. Надо прикручивать jit к компилятору.


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

                                                            Потому что это ломает вывод типов при их использовании.


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


                                                            Плюс, есть OverloadedLabels, но я пока не особо видел, чтобы оно как-то применялось в реальном коде.


                                                            Плюс, есть classy lenses.

                                                              +1

                                                              haskell-gi генерит биндинги с OverloadedLabels, получается очень удобно

                                                                +1

                                                                Никогда раньше не натыкался на эту библиотеку. Примеры кода выглядят интересно, да.

                                                                0
                                                                Потому что это ломает вывод типов при их использовании.
                                                                Инициализация — тип указывается явно. Если в сигнатуре не указано, то почему не сгенерировать несколько, а потом при вызове не подставить нужную?
                                                                  +1

                                                                  Именно это и делает DuplicateRecordFields, посмотрите там примеры, что становится возможным.

                                                                0
                                                                почему нельзя в одном пространстве имён объявить два типа с одинаковыми именами полей

                                                                Потому что каждое имя поля — ещё и функция, которая потом попадёт в это самое пространство имен...

                                                                  0
                                                                  Потому что каждое имя поля — ещё и функция, которая потом попадёт в это самое пространство имен...
                                                                  Имхо это всё же зависит от дизайна языка. В том же elm c этим проблем нет
                                                              –1
                                                              Хорошо что хоть не полчаса собирался.

                                                              Типичное «слышал звон». cpp -E в студию.
                                                            –4
                                                            Визитор позволяет нашим проходам анализа обращать внимание только на части AST, в которых они нуждались, вместо того, чтобы сопоставлять шаблон по всей структуре AST. Это экономит много кода.

                                                            Ой, оказывается те другие попросту использовали «сопоставлять шаблон» и это было плохо. Но это ведь была проблема С++, а теперь оказывается rust-код отказывается от pm в пользу визиторов, которое в С++ попросту нативны(в отличии от rust, где это делается через тысячи костылей и боейлерплейта).

                                                            Каждая новая строчка пробивает очередной дно.
                                                              –14
                                                              Гадящие в карму адепты раста. Вы так и будите из треда в тред сливаться и гадить? Где же ваш код, где же ваши ответы? Нету, не можете? Зачем гадите?
                                                                +22
                                                                Совет. Не использую ни раст, ни С++, но чем больше вы используете фразы «Каждая новая строчка пробивает очередной дно», «сливаться и гадить», «гадящие в карму», «бред это ваши рассуждения вызванные вашем же непониманием», «да и попросту враньё», тем больше вам будут снижать карму, причем даже если вы писали по делу.

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

                                                                P.S. Я вам минусов в карму и комментарии не ставил.
                                                                  –19
                                                                  Совет. Не использую ни раст, ни С++, но чем больше вы используете фразы «Каждая новая строчка пробивает очередной дно», «сливаться и гадить», «гадящие в карму», «бред это ваши рассуждения вызванные вашем же непониманием», «да и попросту враньё», тем больше вам будут снижать карму, причем даже если вы писали по делу.


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

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

                                                                  Именно адептов. Эти адепты набегают табунами из всяких чатов и прочих сборов и минусуют. Это мною доказанный факт.

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

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

                                                                  Всё это было мною разобрано и доказано тысячи раз. Разберитесь перед тем как кого-то оправдывать.

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

                                                                  Я вам ещё раз советую почитать историю Посмотреть как мне угрожали эти адепты. И где же все честные люди были, когда мне писали откровенные угрозы? А, никого не было.
                                                                    –15
                                                                    Адепты увидели ни на чём не обоснованный трёп и начали плюсовать того, кто нагло врёт и оправдывает их. И гадят, гадят. Ведь ничего иного они не могут.
                                                                      +15

                                                                      У вас бред преследования, обратитесь к врачу.

                                                                        –7
                                                                        У вас бред преследования, обратитесь к врачу.

                                                                        Отличная история. Адепты гадят и делают вид будто бы ничего не было. Ответы будут? Ответы за угрозы, ответы за то, что гадят? Где ответы? А Нету ответов, есть только «Гадят» и нелепые обвинения уровня начальной школы
                                                                          +8

                                                                          Я вас ни в чём не обвиняю, просто читая ваши комментарии складывается впечатление, что вы не совсем адекватны (именно реакция на критику, минусы в "карму" и т. п.). Почему вас так волнует, что кто-то в интернете поставил вам минус в какую-то "карму", как от этого изменится ваша жизнь? Социальный рейтинг у нас ещё пока не ввели. Почему вы так агрессивно и остро реагируете? Симптомы бреда преследования прослеживаются (какая-то группа лиц желает зла, гадит и угрожает).

                                                                            –10
                                                                            (именно реакция на критику, минусы в «карму» и т. п.).

                                                                            В чём она неадекватно? В том, что обиженные адепты раста гадят в карму и минусуют, набегая на меня табунами?

                                                                            Я вам так же советую изучить тему перед тем как делать какие-то выводы. Это уже не первая тема в которой они гадят. В корой угрожают. В которой они проигрывают.

                                                                            Почему вас так волнует, что кто-то в интернете поставил вам минус в какую-то «карму», как от этого изменится ваша жизнь?

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

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

                                                                            У меня всё задокументировано. А вы просто кидаетесь нелепыми обвинениями.
                                                                        +27
                                                                        Я не адепт, но я поставил вам минус: вы общаетесь без уважения к собеседнику(даже если вы считаете, что конкретный собеседник вашего уважения недостоин, есть еще другие пользователи, которые вынуждены читать ваши ругательства), а кроме этого, еще и параноите, говоря, что кто-то там набегает и вас минусует, хотя ваши минусы вполне объясняются первым пунктом.

                                                                        И да, я не хочу видеть ваши комментарии на хабре, мне неприятно, поэтому и голосую против вас — это способ саморегуляции сообщества.
                                                                        На самом деле, вам не очень сложно побороть мое мнение и мнение других пользователей: достаточно просто приносить сообществу пользу, превышающую негатив от комментариев, например, писать полезные статьи.
                                                                +3
                                                                Несодержательно. Гораздо интереснее бенчмарки и сравнение временных затрат на разработку. Например, на С++ x человеко-часов, на Rust — y, на Python — z. Ну особенно интересно сравнить в этой задаче производительность Rust и C++.
                                                                  +4
                                                                  К сожалению такой подход будет содержательным, только когда у команд одинаковый опыт, и задача решается с использованием одних и тех же алгоритмов
                                                                  +12

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


                                                                  И я почему-то уверен, что если бы такой курс был, мало кто из наших студентов использовал бы немейнстримные языки типа Haskell/Rust/Okaml/Scala.


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


                                                                  • В шаде на курсе по С++ писал интерпретатор лиспа. Как мне показалось, для подобных штук плюсы не очень удобно использовать, код получается довольно громоздким. Если бы я писал код на скале, было бы в 2-3 раза короче. Меня удивляет, что в статье выше код на плюсах получился не таким уж и большим — видимо, кто-то познал дзен и очень хорошо выбрал уровень абстракций.
                                                                  • Ещё (там же в шаде) на курсе по питону была домашка с интерпретатором питоновского байт-кода, написанном на питоне. Но, видимо, мой мозг слишком привык к статической типизации, т.к. динамическими фишками питона я почти не пользуюсь и в большинстве задач код получается таким же или дальше длинее, чем в скале.

                                                                  P.S. А где вообще есть хорошие хардкорные курсы по классическому CS с построением компиляторов, алгоритмами и прочим? Интересуют как онлайн курсы, так и аспирантура в России/за границей.

                                                                    +2
                                                                    СПбГУ, Булычев или Григорьев (лаба языковых инструментов JB)
                                                                    Будет Вам млн CS во всех проявлениях
                                                                      +9
                                                                      Можно говорить, что российское образование впереди планеты всей и хакеры тоже, но на практике у меня в институте подобного курса в программе не было


                                                                      Это Миф. Я разговаривал со своим боссом (который получал образование у нас и в штатах и создал международную ИТ компанию с филиалами в России, соотвественно мог сравнить образование у нас и Западе). Вот он говорил «В России отличное школьное образование, отвратительное высшее и хорошие программисты». Вот такой парадокс.

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

                                                                      На Западе профессия программиста не настолько привлекательна, плюс школьное образование дает относительно хорошую базу, а высшее в РФ… ИМХО, проигрывает даже простому самообразованию, может не во всех программерских вузах, но во многих.
                                                                        +2
                                                                        Как уже было замечено в наших краях хорошее школьное образование, плохое высшее и отвратительные магистратуры, в США всё наоборот.

                                                                        Возвращаясь к компиляторам: есть курс от computer science center www.youtube.com/watch?v=DKr45aBUtFU&list=PLlb7e2G7aSpQ4Ym2TWTYyMcfMevxpKoxj

                                                                        Он там не один такой, есть прочитанные в другое время. Я сам не интересовался, за качество отвечать не берусь, но ознакомиться можете.
                                                                          +1
                                                                          P.S. А где вообще есть хорошие хардкорные курсы по классическому CS с построением компиляторов, алгоритмами и прочим? Интересуют как онлайн курсы, так и аспирантура в России/за границей.

                                                                          У JetBrains и Computer Science Center вроде интересная магистратура в ИТМО, вот программа, а вот темы работ недавних выпусников. Конечно, много прикладного, но какой-то чел защищался по завтипам, например.
                                                                          +3

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

                                                                            +4
                                                                            Думал будет всестороннее сравнение: скорость разработки, скорость выполнения, читабельность итд…
                                                                            А тут просто wc -l сделали.
                                                                              –9
                                                                              Отлично! Чтд. Динамическая типизация рулит. Таскать с собой зверское мозголомство с ковариантными и контрвариантными шаблонами чтобы случайно переданную str вместо int отловил компилятор, при том что такая конструкция завалится на первом же тесте — ну такое.
                                                                                +9

                                                                                Да вроде бы в том и плюс — не писать тесты там, где может справиться компилятор/интерпретатор.

                                                                                  0
                                                                                  в статье как раз есть абзац про трудоемкость описания системы типов, при том что тесты придется прогонять и там и там.
                                                                                    +3

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

                                                                                      0

                                                                                      вот этого мы не узнаем… тем более по условию этого задания все писали на один раз.

                                                                                      +1

                                                                                      Я, наверное, слишком быстро читал, но пропустил этот абзац.


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

                                                                                        0
                                                                                        Одна большая часть этого различия, вероятно, динамическая типизация. Только в нашем ast.rs 500 строк определений типов, и ещё много типов, определённых в других местах компилятора. Мы также всегда ограничены самой системой типов. Например, нам нужна инфраструктура для эргономичного добавления новой информации в AST по мере прохождения и доступа к ней позже. В то время как в Python вы можете просто установить новые поля на узлах AST.
                                                                                          +5

                                                                                          Так именно ради этого и есть trees that grow. Притом это не «напихали чо-т в словарь, вроде работает», а полноценные статически проверяемые тайпчекером аннотации.


                                                                                          Другое дело, что в Rust это нереализуемо (согласно тому, что я знаю о его системе типов).

                                                                                            0

                                                                                            что то это не помогло Хаскелю в этот раз

                                                                                              +1

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

                                                                                      –1

                                                                                      за счет 2х времени и 3х обьема кода в котором так же можно ошибится… так себе плюс.

                                                                                    +2
                                                                                    Сравнение одинакового проекта в Rust, Haskell, C++, Python, Scala и OCaml
                                                                                    А в каком месте он одинаковый? Алгоритмы разные, абстракции разные, готовность разная, количество дополнительных функций тоже разное. Тем более, что больше всего отличаются друг от друга реализации на одном и том же языке. Почему код меряют в байтах и строках?
                                                                                      0

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

                                                                                        +1
                                                                                        Так скажем, задача была поставлена одинаковая.
                                                                                        +1
                                                                                        К слову, Waterloo самый лучший университет в Канаде в компьютерной области и пожалуй один из самых в мире. Там, к примеру, написали знаменитый Watcom и создали Maple. PHP, QNX и BlackBerry тоже оттуда же.
                                                                                          +1

                                                                                          Не кажется ли вам, что php лишний в этом списке? Это, конечно, целая эпоха, но вспоминать про нее не хочется
                                                                                          Ко всем остальным питаю теплые чувства

                                                                                            0

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

                                                                                              0

                                                                                              Для человека, который хочет просто в WISIWYG-редакторе накидать страницу, вообще без разницы, что там под капотом. Он просто возьмет Тильду, ВордПресс, что там еще модно — wix — как сервис, не вдаваясь в детали реализации.

                                                                                          –2

                                                                                          Отичный пример почему динамическая типизация это очень круто если применять правильно!
                                                                                          Кода гораздо меньше и результат получен одним человеком а не двумя-тремя.

                                                                                            –2
                                                                                            Ща минусов отхватите за компанию
                                                                                              0
                                                                                              что за маньяки ставят минус за динамическую декларацию в 21 веке? Это же не значит что переменная действительно должна быть object))
                                                                                                +3
                                                                                                Вначале выходит новый-прогрессивный язык без типизации, потом на нём пишут кучу кода, а потом, спустя время к нему через костыли в отдельных местах начинают прикручивать статическую типизацию, которая во-первых работает не везде, во-вторых требует огромной работы по переписыванию, написанию биндингов и так далее. Какой популярный динамический язык избежал подобной участи?
                                                                                                  0

                                                                                                  Lua, вероятно.

                                                                                                  0
                                                                                                  варианты не только «без типизации» и со «статической типизацией». Опять 25 — моё предложение — возможность включать / отключать типизацию, плюс инспекция действительно ли переменная динамическая, или её можно заменить одной (вроде auto ) или несколькими статическими в препроцессинге
                                                                                                  Pyton фактически избежал типизации потому что arg:type толком не работает, сам тестил туда без какого либо сообщения пролезает тип который там быть не должен.
                                                                                                  Да щас я тут кучу минусов понахватаю)
                                                                                                    +1
                                                                                                    моё предложение — возможность включать / отключать типизацию

                                                                                                    А зачем статическую типизацию отключать? Это всё равно что посадить льва в открытую клетку — рано или поздно но лев выйдет. Рано или поздо, кто-то в своей библиотеки да отключит типизацию и заразит таким образом весь оставшийся код

                                                                                                    Большинство рассматривает статическую типизацию как это реализовано в си, в лучшем случае в крестах или яве. Прогресс неумолим и уже появились гораздо более подходящие варианты.
                                                                                                    плюс инспекция действительно ли переменная динамическая, или её можно заменить одной (вроде auto ) или несколькими статическими в препроцессинге
                                                                                                    Возьмите crystal. Вот это вот не собирается.
                                                                                                    if Random.rand > 0.5
                                                                                                      test = "text"
                                                                                                    else
                                                                                                      test = 5
                                                                                                    end
                                                                                                    
                                                                                                    puts test.upcase
                                                                                                    А вот это — собирается
                                                                                                    if Random.rand > 0.5
                                                                                                      test = "text"
                                                                                                    else
                                                                                                      test = 5
                                                                                                    end
                                                                                                    
                                                                                                    puts test.is_a?(String) ? test.upcase : test
                                                                                                    Типизация на месте, ошибок в рантайме нет.
                                                                                                    Pyton фактически избежал типизации потому что arg:type толком не работает, сам тестил туда без какого либо сообщения пролезает тип который там быть не должен.
                                                                                                    Не даром я не любливаю python, ибо не смотря на свой дзен он умудряется быть непростым, непонятным и неявным. В итоге люди будут писать код который ещё и не работает
                                                                                                      –1
                                                                                                      Да уж, дзен питона где везде надо таскать self, которого становится по пол экрана, но при этом магическая комбинация подчеркиваний превращается в другую магическую комбинацию подчеркиваний молча — он прекрасен. Тем не менее питон — очень мощная штука, система типизации с динамическими атрибутами — это оч клево. Уверен на следующем витке чуток причесав синтаксис из этого сделают отличный инструмент.

                                                                                                      Возвращаясь к Вашему примеру с crystal — это прекрасно, что такое отловит компилятор, но тут меня неизбежно всегда мучает вопрос про троллейбус из буханки. Да мы отловим на этапе компиляции некоторый весьма ограниченный набор багов, которые, на самом деле, одни из самых тривиальных. Но никакой компилятор не отловит ошибки в логике, да те же перепутанные местами аргументы одного типа, с которыми уже можно всего веселого наворотить, типа copy(input_file, output_file), а вызвать наоборот. Т.е. отбрасывание (ценой повышения трудоемкости написания кода в разы) заведомо небольшого количества проблем почему то постулируется как повышающее надежность до недостижимых другими методами высот. Что есть самообман, по-моему. То же TDD или контрактное программирование здесь выглядят гораздо более системным.

                                                                                                      Вообщем придерживюсь мнения что статическая типизация суть анахронизм, абсолютно необходимый в компилируемых языках прошлого поколения, когда требуется точное знание размера бинарного представления объекта на этапе компиляции, там да. А коли у нас объект — это бирка с типом и счетчиком ссылок в куче — ну какой смысл таскать его тип в коде — понимать отказываюсь.
                                                                                                        0
                                                                                                        Да мы отловим на этапе компиляции некоторый весьма ограниченный набор багов, которые, на самом деле, одни из самых тривиальных.
                                                                                                        Что быстрее — компиляция или прохождение всех тестов? А если используется библиотечный код? Что быстрее — указание типа или написание достаточного количества тестов? Насколько легко среде разработки будет указать на то, что тут неверный тип(например после рефакторинга)?
                                                                                                        А коли у нас объект — это бирка с типом и счетчиком ссылок в куче
                                                                                                        Счётчики ссылок это дорого, так как при каждом присваивании/выходе из зоны видимости их нужно обновлять. От циклических ссылок они не защищают. Единственное, зачем они нужны — избежать остановки на сборку мусора.
                                                                                                          +4
                                                                                                          Единственное, зачем они нужны — избежать остановки на сборку мусора.

                                                                                                          И вот выходит у Вас из области видимости развесистое дерево умных указателей с подсчетом ссылок…
                                                                                                            +1

                                                                                                            Дерево как раз нет, а какой-то граф с циклами вполне

                                                                                                              0
                                                                                                              Циклы тут даже могут облегчить ситуацию, хотя память, да, может утекать. А вот stop world из-за одновременного обнуления счетчиков и освобождения многих объектов — нет-нет да и да…
                                                                                                                0

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

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

                                                                                                                    Питон на основе подсчёта ссылок работает и деструктор вызывается сразу как объект покидает скоуп если счётчик стал равен нулю, а GC только для циклических случаев.
                                                                                                                    А на С/C++ подсчёт ссылок не нужен если иерархия владельцев ресурсов известна заранее.

                                                                                                                      –2
                                                                                                                      Я не знаток управления памятью в python, но есть два вопроса:
                                                                                                                      1. Вы уверены, что объект с ненулевым счетчиком освобождается по выходы из области видимости,
                                                                                                                      2. В питоне есть деструкторы? Прям у всех-всех типов? Как его задают для пользовательских типов?
                                                                                                          +3

                                                                                                          Я с Вами соглашусь и не соглашусь. Соглашусь по поводу того, что проблемы в логике кода не отловить. А не соглашусь, что статическая типизация анахронизм (или анахрЕнизм — кому как больше нравится, в зависимости от отношения). Я действительно хочу быструю компиляцию и быстрый код, а языки с динамической типизацией отстают в этом аспекте (но это очень холиварная тема). А еще чтоб среда подсказывала что и где (в случае динамической нестрогой типизации — среда тоже будет путаться и подсказывать не то или вообще не подсказывать).
                                                                                                          С другой стороны, тот же Питон — прекрасен для БЫСТРОГО прототипирования. Здесь он вне конкуренции.

                                                                                                          –1
                                                                                                          стоп, вышеописанный код для тебя — статическая, динамическая типизация или безтипная? потому что тут может быть и статическая смотря как работает rand))
                                                                                                          если потом будет присвоение другому типу там test = 42.0, согласен в питоне есть ряд вещей которые я бы подкоректировал или поудалял, сделав его таким языком чтобы других ненадо было ))
                                                                                                            0
                                                                                                            стоп, вышеописанный код для тебя — статическая, динамическая типизация или безтипная?
                                                                                                            Сильная статическая неявная. Все три слова важны. Тип данной переменной String | Int. Это означает, что каждый раз, когда с переменной что-то происходит, компилятор проверяет, может ли данное действие быть совершено над данным типом. Метод is_a? есть и у String и у Int. По этому, компилятор принимает безусловный вызов метода. А вот upcase есть только у String и по этой причине компилятор требует явной проверки типа. Компилятор достаточно умён, чтобы понять что в первой ветке тернарного оператора тип String и не требует обязательного приведения типов.
                                                                                                            потому что тут может быть и статическая смотря как работает rand))
                                                                                                            Рекомендую поставить на машину и поиграться.
                                                                                                              0
                                                                                                              а с чемньть типа

                                                                                                              a=[1,"str", [1,2]]
                                                                                                              for i in a: i.upcase 
                                                                                                              

                                                                                                              оно справится? Ибо самое интересное про типы, как известно, начинается в коллекциях.
                                                                                                                +1
                                                                                                                Crystal это ближе к ruby, чем к python.
                                                                                                                a = [1, "str", [1, 2]]
                                                                                                                a.map do |i|
                                                                                                                  if i.is_a?(String)
                                                                                                                    puts i.upcase
                                                                                                                  else
                                                                                                                    puts i
                                                                                                                  end
                                                                                                                end
                                                                                                                

                                                                                                                Или то же самое, только в две строки
                                                                                                                a = [1, "str", [1, 2]]
                                                                                                                a.map { |i| i.is_a?(String) ? puts i.upcase : puts i }

                                                                                                                Допустим функция не принимает тип Int, а я ей его передаю.
                                                                                                                a = [1, "str", [1, 2]]
                                                                                                                
                                                                                                                def func(arg)
                                                                                                                  arg.map do |i|
                                                                                                                    if i.is_a?(String)
                                                                                                                      puts i.upcase # Строка
                                                                                                                    else
                                                                                                                      puts i.uniq # Массив
                                                                                                                    end
                                                                                                                  end
                                                                                                                end
                                                                                                                
                                                                                                                func(a)

                                                                                                                Тогда вот что скажет компилятор

                                                                                                                Error in line 13: instantiating 'func(Array(Array(Int32) | Int32 | String))'

                                                                                                                in line 8: undefined method 'uniq' for Int32 (compile-time type is (Array(Int32) | Int32))

                                                                                                                Rerun with --error-trace to show a complete error trace.


                                                                                                                Вот ещё один пример
                                                                                                                def func(arg : String | Array)
                                                                                                                  puts arg.size # метод есть у строк и у массивов
                                                                                                                end
                                                                                                                
                                                                                                                func("текст")
                                                                                                                func([0, 1, 2, 3])
                                                                                                                Если нужно, можно явно указать тип переменной.
                                                                                                  +2
                                                                                                  С тем же успехом можно и в C# писать как на javascript и получится ненамного многословнее. Вместо
                                                                                                  val['sum'] = val['a'] + val['b']

                                                                                                  Будет
                                                                                                  val["sum"] = (int)val["a"] + (int)val["b"]

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

                                                                                                  ИМХО динамическая типизация хороша либо для скриптов которые можно написать за пять минут, либо как промежуточное представление.
                                                                                                    –1
                                                                                                    С тем же успехом можно и в C# писать как на javascript

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

                                                                                                      0
                                                                                                      Аргументы функции вы не сделаете динамическими.
                                                                                                      Пример?
                                                                                                        +3

                                                                                                        А params object[] на что?


                                                                                                        Если же совсем-совсем динамика нужна, то в C# и тип dynamic есть...

                                                                                                        +1

                                                                                                        Достаточно юзать dynamic, только смысла ноль.

                                                                                                          0

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


                                                                                                          Получилось даже забавно, можно в отдельную библиотечку оформить.

                                                                                                        0

                                                                                                        О результатах мы мало что знаем, особенно о стоимости отладки и доработки.

                                                                                                          0

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

                                                                                                        +1
                                                                                                        В книге С.Свердлова «Языки программирования и методы трансляции» (2007) транслятор учебного языка реализован на нескольких языках: Паскале, Си, Обероне, Яве и Сишарпе. Это не только иллюстрирует принципы кросс-трансляции, но и позволяет получить множество сравнительных характеристик. На стр.400 эти характеристики сведены в таблицу.
                                                                                                        Рецензия на книгу по ссылке.

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

                                                                                                        Самое читаемое