Pull to refresh
4
0
Send message

Good to know, thanks — looks like i was fooled by a random "tutorial" page that pops up in search results at the top.

Посмотрите на https://github.com/Ludeme/Ludii — недавно выложенный на Гитхабе исходный код активного проекта по теме с довольно длинной историей.

Отлично! У меня (пишу на C++ на работе) под рукой пара рекомендаций (я сам стараюсь быть в курсе развития языка, хотя именно на работе далеко не всегда имеется возможность применить те или иные улучшения):


https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines — живой, развивающийся документ, рекомендации от ведущих специалистов комитета стандартизации.


https://github.com/changkun/modern-cpp-tutorial — учебник по основным новым фичам, появившимся в последних 4-х итерациях стандарта.


Есть пара очень недавно вышедших книг (у одной автор Slobodan Dmitrović, у другой Jason Turner), не могу уверенно рекомендовать, так как сам не прочитал, листал только.


Оговорюсь, что может показаться, что стремление использовать новые возможности языка — сходна к желанию поиграть в новые блестящие игрушки. Это не совсем так! Изменения и нововведения делаются для решения определённых проблем, они действительно полезны в долгосрочной перспективе. Документ Cpp Core Guidelines иногда объясняет смысл нововведений, я часто в него заглядываю по практической нужде.

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

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


Меня больше расстраивает, что автор пишет на смеси некоторых элементов современного C++ (constexpr, enum class) и низкоуровневого C (везде указатели, не ссылки, рискованная адресная арифметика, невнятные отношения владения без смартпойнтеров — сырой указатель на вектор токенов tokens, пары указатель / длина) — не понятна цель упражнения "вспомнить C/C++", зачем это вспоминать, можно же писать более чисто и безопасно на современном C++. Жалко.

Согласен с аргументом в пользу C++ бэкенда (про использование конкретных удобств, перегрузки операторов в вашем случае). Меня беспокоит нетривиальная модель жизненного цикла объектов в C++ — в случае, если C++ бэкенд на неё закладывается, она может протечь в семантику разрабатываемого языка, привнося риск искусственного усложнения. Если в генерируемый C++ коде не опирается на автоматический вызов деструкторов, жизнь проще… Однако, если, например, поддаться соблазну использовать хотя бы unique_ptr, вещи усложнятся.


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

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


Первое. Транспиляция в C (не в C++) может быть более гибким решением, когда вы будете добавлять понятие объекта. Семантика управления жизни объектов С++ — довольно специфическая, привязка к ней, с одной стороны, ограничит выбор возможностей, а, с другой стороны, повлияет на уровень сложности не в лучшую сторону. В качестве примера языка, старающегося избегать эти проблемы, приведу Nim. Его флагманский бэкенд — C (C++ бэкенд есть, но он альтернативен и, вероятно, не поддерживает недавно добавленные возможности Nim). В C++ деструктурирование объекта, даже уже перемещённого куда-либо, обязательно — поэтому перемещающие конструкторы, операторы присваивания должны заботиться об "опустошении" перемещённого объекта, чтобы при выполнении деструктора ничего не происходило. Такое (необязательное, если проектировать язык с ноля) усложнение специфично для C++, поскольку move-семантики не было в начальной версии языка, а когда она была введена позже, её нужно было "гладко" примирить с предшествовавшими правилами (диктующими обязательность вызова деструктора). В Nim, насколько я понимаю, эта лишняя сложность преодолевается: деструктор — лишь один из способов уничтожить объект, одна из потенциально нескольких "поглощающих" объект функций (хотя и наиболее стандартная и частоупотребляемая). Не знаю, как авторы Nim примеряют это c правилами C++ в C++-бэкенде языка (если оный не заброшен). Также, транспиляция в C кардинально упрощает проблемы FFI, бинарной совместимости раздельно скомпилированных библиотек. У C++ с этим — большие проблемы. Любопытное стороннее ответвление темы про объекты — в языке R более одной "системы объектов", запрограммированной поверх ядра языка в виде библиотеке.


Второе. В ваших примерах вы объединяете последовательное выполнение ("императивное") с бэктрекингом в духе Пролога ("декларативное" — под которым, на самом деле, в Прологе подразумевается более сложное императивное исполнение — с бэктрекингом; в частности, в Прологе порядок выражений, перечисленных в правой части через запятую, играет важную роль в плане производительности и вообще завершаемости исполнения "декларативной" программы). Обратите внимание на концепцию алгебраических эффектов (хорошо развитый пример — Koka, который, кстати, в новой версии тоже транспилируется в C), объединяющую под одим зонтом не только эти два аспекта, но и эффекты другого рода — исключения, завершаемость, асинхронность, недетерминизм, сторонние эффекты (state), асинхронное исполнение… (язык позволяет программировать новые эффекты кроме определённых в базовой библиотеке). У Koka — солидный математический фундамент: система алгебраических эффектов обладает свойствами, похожими на комбинирование монад в Haskell, при более компактных "церемониях", ложащихся на плечи программиста, использующего язык. Кстати, ещё один интересный аспект Koka — минималистический синтаксис, например, синтаксис while { тут условие } { тут действие } — это не встроенная конструкция языка с ключевым словом while, а вызов функции while, принимающий два параметра — анонимные функции. В Nim есть похожие синтаксические идеи, но Koka — более изящен в этом плане.


Третье — система гигиенических макросов может обеспечить минимальность ядра и богатый, знакомый "программистам на языке ..." синтаксис запрограммированный в базовой библиотеке поверх макросов. На ум приходит Racket (потомок Scheme, язык с богатыми средствами построения DSL) — к сожалению, для использования Racket нужно погрузиться в его лиспоподобный мир. С другой стороны, система гигиенических макросов регулярно появляется в современных языках (примеры: Nim, Rust, Julia) — это хороший способ сохранять ядро языка компактным, а богатство (выразительность) синтаксиса выносить в библиотеки.

"В реальном алгоритме приведет к n операций умножения" — это если в лоб, можно же быстрее (log n). Материалы на Хабре по теме:
N-е число Фибоначчи за O(log N)
(вместо формулы Бине используется быстрое вычисление по рекуррентной формуле посредством возведения матрицы в степень — сложность та же)
Используем быстрое возведение матриц в степень для написания очень быстрого интерпретатора простого языка программирования
(интересное развитие темы с обобщением на широкий класс рекуррентных последовательностей).
Вижу комментарий от eandr_67 ниже, в котором тоже упоминается сложность O(log(n)) — но без ссылок на существующие заметки, так что публикую ссылки здесь, мало ли, кому будет интересно.

Зашёл на эту запись по ассоциации, прочитав свежий пост izvolov -а.


Да и книги вышли внезапно, относительно недавно. Я узнал о двух из них, слушая подкаст "CppCast", Jason Turner — его соведущий. С Слободаном у них был выпуск (Джейсон как раз свою книгу писал тогда, расспрашивал Слободана), а потом о выходе книги Джейсона упомянули между делом. Книга Чангкуна не такая новая; она в свободном доступе (на Гитхабе) и, вероятно, обновляется.


Ожидаю, что в ближайшее время появится несколько новых редакций книг, в названии которых "C++17" будет заменено на "C++20": тот же эффект, как три года назад (когда в названии ставили C++17). Я тогда тоже поверхностно пролистал несколько книг, но не был впечатлён (попадалась откровенная халтура).

Недавно вышел именно учебник, "Modern C++ for Absolute Beginners" (подзаголовок: "A Friendly Introduction to C++ Programming Language and C++11 to C++20 Standards"), Slobodan Dmitrović. Есть ещё пара недавних книг (не учебников), в которые я заглядывал: "Modern C++ Tutorial: C++11/14/17/20 On the Fly", Changkun Ou и "C++ Best Practices", Jason Turner. От комментариев, что в этих книгах хорошо / плохо воздержусь, заглядывал только поверхностно. Возможно, стоит обратить внимание на вышедшее летом 2018-го второе издание "A Tour of C++ (C++ In-Depth Series)", Bjarne Stroustrup.

Could you please correct this fragment (following up with correspondent corrections below it)?


In our use case, however, we will be just fine with the maximum value for an int64. Any of these will do:

minLn := 1 << 63 - 1

minLn := math.MaxInt64

The first variant is incorrect — and you picked the incorrect one out of two! Worse, you formatted this bad expression later on as 1<<63 - 1 — no spaces around << to kind of stress the fact that as you think this operation has the higher precedence over -.


No it does not. You need parentheses around 1 << 63. Go has C-style precedence of operators and - is one step higher in the precedence order than <<, so your expression yields the same result as 1 << 62 which is not what you meant. Check this table, for example: https://www.tutorialspoint.com/go/go_operators_precedence.htm.


Please correct!

Я первым делом заглянул в Википедию, там есть небольшой список литературы.
ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D1%8F%D1%8F_%D0%B0%D0%BB%D0%B3%D0%B5%D0%B1%D1%80%D0%B0
en.wikipedia.org/wiki/Exterior_algebra
Английская статья — значительно более обстоятельная.

У меня есть математическое образование — просмотр статей в Википедии расставил вещи более по местам в моей голове (после состояния лёгкого сумбура, вызванного чтением заметки). Вроде бы вектроное произведение векторов входит в школьный курс геометрии (да, оно ведь и в школьном курсе физики должно использоваться), я бы на месте автора подключил соответствующие ассоциативные связи ранее при изложении.
Спасибо за информацию! Стало любопытно, приладил ли кто-либо к Taskflow couroutine синтакс. Готового не нашёл, но должно быть не сложно (по стопам std::future), если буду экспериментировать с Taslflow, попробую.
А не попадалась ли вам вот эта книга: www.state-machine.com/psicc («Practical Statecharts in C/C++») — старая, но довольно любопытная. Автор разработал свою библиотеку для реализации иерархических FSM (предыдущее издание называлось «Hierarchical State Machines in C/C++»), программируя для embedded-устройств (GPS, как мне помнится).
Спасибо и вам! При случае (если вам захочется) можно обсудить, так ли уж магичны смартпойнтеры (после того, как вызовы delete в деструкторах / завершениях функции начнут казаться студентам чем-то, заслуживающим обобщения как переиспользуемый, один раз реализованный «паттерн автоматического управления памятью в C++») и как объявлять функцию так, чтобы её взаимоотношения на счёт владения объектами с вызывающим кодом были ясны уже из объявления, без необходимости заглядывания в реализацию — но это уже C++-специфично, к вашему курсе, вероятно, плохо подойдёт.
Вы правы, в курсе по ООП лучше использовать Java, C#. Однако, если вы выбрали C++, пожалуйста, не демонстрируйте студентам код от преподавателя с ошибками в управлении памятью. В вашем первом примере (с беззаботной по части управления памяти передачей ответственности в DivisionChecker)

queue = new DivisionChecker(i, queue);

классическая, учебниковая утечка памяти (вызывающий код, вероятно, предполагает передачу владения старого queue в новый экземпляр DivisionChecker, который к этой неожиданной для него дополнительной задаче относится несколько беззаботно). Можно использовать как простую задачу на зачете или разогревный вопрос на интервью.
Здравствуйте, я хотел бы добавить свой комментарий в этом обсуждении исключительно потому, что вы показываете этот код студентам (ну и здесь вы пометили материал как Tutorial). Пожалуйста, потратьте какое-то количество времени на прочистку — на благо вашим студентам и человечества в целом. Используйте std::unique_ptr вместо T* там, где структура данных владеет объектом T (и, в целом, постарайтесь избегать * по возможности, используйте &). Используйте nullptr вместо NULL (в объявлении чистых виртуальных методов пишите "= 0", не "= NULL"). Определитесь, как вы обозначаете перекрытие вирутального метода в наследующем классе (я бы однозначно рекомендовал override — у вас в коде это ключевое слово отсутсвует, а virtual то есть, то нет). Используйте !X вместо X == false.

Дайте знать в случае, если вам нужны пояснения к этим рекомендациям: я попробую их предоставить (ссылаясь, например, на C++ Core Guidelines: isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html).

Про «абстракцию указатель на указатель» — может оказаться опасной педагогическая затеей, на мой взгляд, при отсутствии базовых понятий об управлении памятью. Работая с аллоцируемой в куче памятью на C++, прежде всего, учите пользоваться std::unique_ptr (плюс std::make_unique — чтобы в коде решения задачи не светились не только явные delete, но и явные new) и & ссылками с первого дня. Окупится сторицей. Если нужен дополнительный материал для пытливых студентов, дайте им std::shared_ptr (c std::make_shared) и настоятельно отговаривайте от использования «голых» C указателей, new, delete.
Отдельно номинативная и отдельно структурная типизация есть из коробки также в Nim — это более молодой (в сравнении с OCalm) язык, компилируемый в C, С++, JavaScript. Структурные типы (tuple) и номинативные типы (object) в Nim довольно унифицированы в плане синтаксиса. Структурные типы предоставляют больше удобств, изящно сочетающихся с структурной типзацией.

nim-lang.org/docs/tut1.html#advanced-types-objects

Про ваш случай с бойлерплейт-кодом на C# — возможно, продуманное применение Source Generators могло бы ситуацию улучшить (я редко прогаммирую на C#, но при случае попробую новую фичу, плюс record types).

Да, и поскольку F# — своебразный порт OCalm на .NET, скорее всего, вы увидите там типизацию в стиле OCalm (на вскидку не могу уверенно сказать, помню только, что record типы в F# есть давным-давно, тут C# догоняет F#).
KD637 в ваш перевод просочилась довольно забавная ошибка. В оригинале указано BB(2) = 6 и BB(3) = 21 (а не 107, как в вашем переводе) — и не указано BB(4), за этим знанием надо обратиться к статье 1983го года… Чтобы узнать, что BB(4) = 107! Так вот откуда взялось неверное значение BB(3)! :)
Я так понял, внутриполитическая логика была такая: партия / движ переписать приложения с нуля заполучило мощного союзника — отдел iOS — но при условии того, что переписывание для iOS будет на Swift. Сюжетная линия, что Android-приложение тоже переписывалось с нуля, провисла, поскольку автор не в отделе Android работал. Далее, предполагаемая логика отдела iOS: а) рано или позно переходить на Swift надо, почему не воспользоваться моментом; б) Swift — современный модный хайповый язык, приобретаемые навыки перспективны, возможности привлечения молодых программистов лучше.

Кстати руководству пункты а) и б) могут быть вполне понятны, они головную боль перетерпят.

Information

Rating
5,627-th
Registered
Activity