суббота, 19 ноября 2016 г.

[prog.c++] Нужно ли делать подробный разбор статьи "Почему не лишено смысла писать код на C, а не на C++"?

Upd. Первая статья серии здесь: Разбор asfikon's "C vs C++". Часть 1..

В начале года я уже писал о странной тенденции -- активной агитации за использование чистого C вместо C++. Похоже, за прошедшее время ситуация стала только хуже :(

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

Так вот, в одном из флеймов в качестве аргумента была дана ссылка на статью ув.тов.asfikon-а "Почему не лишено смысла писать код на C, а не на C++". Тогда я не стал особо заострять внимание на данной статье (ограничившись лишь парой ироничных комментариев). Очевидно, что я недооценил пагубность ее влияния.

Вчера специально просмотрел статью еще раз. Блин, ну какой же бред там написан! Но ведь кто-то воспринимает написанное там всерьез. И это пугает.

Посему, есть мысль взять и обсудить основные тезисы данной статьи.

Однако, бросить лопату говна на вентилятор -- это не сложно, а вот аргументированно разобрать каждый из мифов гораздо сложнее. На это требуется время. И, скорее всего, получится не один пост с разбором. А придется делать целую серию постов. Что не просто, на это нужно изыскивать дефицитное время. Поэтому хочу спросить у читателей: такая серия постов вообще кому-нибудь будет интересна? Стоит этим заниматься?

Пожалуйста, найдите способ высказать свое мнение: комментарием или +1, распространением данного поста, личным сообщением или еще как-нибудь.

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

пятница, 18 ноября 2016 г.

[prog.c++.bicycle] Пытаюсь наваять совсем простенькую функцию n_times

Когда-то уже заходила речь (кажется в G+), что хорошо было бы иметь в C++ функцию n_times для того, чтобы не нужно было циклы выписывать руками. Т.е. вместо того, чтобы писать:

const auto iterations = std::stoul(argv[1]);
forunsigned long i = 0; i != iterations; ++i )
   do_some_action();

А можно было бы изобразить что-то вроде:

n_times( std::stoul(argv[1]), []{ do_some_action(); } );

Ну и в итоге дозрел до того, чтобы добавить n_times в нашу маленькую библиотечку cpp_util. Самый первый, черновой вариант, выглядит вот так:

templatetypename COUNT_TYPE, typename LAMBDA >
void
n_times( COUNT_TYPE n, LAMBDA && body )
   {
      loops_details::ensure_non_negative( n );
      for( ; n; --n )
         body();
   }

В нем вспомогательная функция ensure_non_negative в случае, если COUNT_TYPE является знаковым типом, проверит, чтобы n имел неотрицательное значение. В случае же отрицательного значения будет порождено исключение std::invalid_argument.

И вот тут у меня есть сомнения. Ведь такая проверка может вести к дополнительным накладным расходам. В каких-то случаях компилятор ее выбросит. Но в каких-то оставит. А это может не понравится некоторой части потенциальных пользователей. Посему есть мысль пойти по такому варианту:

using namespace cpp_util_3;

// Пользователь не против получить исключение, если ошибся
// со значением аргумента.
n_times< checked_loop >( std::stol(argv[1]), []{ do_some_action(); } );

// Пользователь зуб дает, что все будет чики-пуки.
n_times< unchecked_loop >( std::stol(argv[1]), []{ do_some_action(); } );

Или по такому:

using namespace cpp_util_3;

// Пользователь не против получить исключение, если ошибся
// со значением аргумента.
n_times( untrusted_n(std::stol(argv[1])), []{ do_some_action(); } );

// Пользователь зуб дает, что все будет чики-пуки.
n_times( trusted_n(std::stol(argv[1])), []{ do_some_action(); } );

Интересно мнение читателей: вам бы какой вариант был бы более удобен? Вообще без проверок? С проверками всегда? С отключаемыми проверками?

При этом если n -- это беззнаковое значение, то оно не проверяется вообще. Тут в дело вступает совсем простая шаблонная магия, которая устраняет избыточные проверки прям в compile-time.

PS. Кроме n_times еще хочется сделать up_to (или for_every_i):

using namespace cpp_util_3;

for_every_i( 010, [](auto i) { std::cout << i << std::endl; } );

Тут так же можно подумать на счет checked_loop/unchecked_loop...

понедельник, 14 ноября 2016 г.

[prog.flame] Прочел тут давеча "Why Rust"

Проглотил давеча за пару-тройку дней небольшую бесплатную книженцию "Why Rust" от O'Reilly. Книжица толковая. Собрает воедино все основные плюшки Rust-а в небольшом объеме текста. Что для меня очень большой плюс, т.к. эпизодические подходы к разным частям официальной документации позволяли разобраться в том или ином отдельном аспекте, но не создавали целостной картинки того, чем же должен брать Rust. Посему, если кто-то хочет составить себе впечатление об этом языке, но не хочет штудировать официальный "The Book", то имеет смысл взять и одолеть "Why Rust".

После прочтения у меня сформировалось несколько пунктов, по которым можно что-то сказать.

  1. Блин, синтаксис реально имеет значение. Первое, что отталкивает от желания связываться с Rust-ом (и отталкивает весьма сильно) -- это непонятное для меня желание экономить на всем. fn вместо func или function, impl вместо implementation, mut вместо mutable, u8 вместо uint8 и т.д.

    Казалось бы, ну синтаксис и синтаксис, можно привыкнуть к большой вариативности синтаксисов, почему же Rust-овский синтаксис так раздражает?

    Думаю, что это из-за высокой плотности содержимого в Rust-овском коде. Т.е. когда ты видишь что-то вроде:

    fn firsts<'a, 'b>(x: &'a Vec<i32>, y: &'b Vec<i32>) -> (&'a i32, &'b i32)

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

    func firsts<borrow A, borrow B>(x: borrow<A> Vector<int32>, y: borrow<B> Vector<int32>) -> tuple<borrow<A> int32, borrow<B> int32>

    Upd. Или вот так:

    func firsts(x Vector<int32> in A, y Vector<int32> in B) -> tuple<int32 in A, int32 in B>

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

    Да и вообще, если язык задумывался как безопасная альтернатива C/C++, то язык следовало бы делать максимально похожим по синтаксису на C и C++, тем более, что шаблоны в С++ и так позволяют записывать довольно сложные вещи. Так что можно было бы придумать нечто вроде:

    template<borrow A, borrow B>
    tuple<borrow<A, int32>, borrow<B, int32>>
    firsts(borrow<A, vector<int32>> x, borrow<B, vector<int32>> y)

    Имхо, мимикрия возможностей Rust-а под синтаксис C++ позволила бы не только упростить вход в язык для C++ников. Но и позволила бы с намного меньшими затратами переделывать уже существующий код под Rust. Если у меня 10MLOC кода на C++, то, понятное дело, на Rust я это все конвертировать не буду. Но, если у меня всего 25KLOC, то вместо того, чтобы переписывать все на Rust заново, я бы мог просто тщательно пройтись напильником по уже имеющемуся у меня коду и с минимальными услилями превратил бы C++ный код в Rust-овый код. Кстати говоря, когда-то на небольших программках я так поступал с переделкой C++ного кода в D-шный код.

  2. У меня складывается впечатление, что Rust стоит относить к той же группе, что и языки вроде Eiffel и Ada. Т.е. на них нельзя "наговнякать что-то по-быстрому". Нужно сесть, подумать, основательно разобраться в том, что нужно сделать, как это сделать, почему именно так. Потом еще побороться немного с компилятором, чтобы договориться о том, где ты ему веришь, а где он тебе должен верить. В итоге получится работающий код, в стабильность и надежность которого ты более-менее веришь (хотя так это или нет -- это отдельный вопрос, т.к. отсутствие утечек памяти и повисших указателей вовсе не гарантирует корректную работу приложения). Но все это будет не быстро, а по-началу и не легко.

    Так что да, если хочешь, чтобы все было "глобально и надежно" и у тебя есть возможность умножить свои сроки и бюджеты на 1.5, а в замен получить увеличение коэффициента спокойного сна на 15-20%, то Rust может быть хорошим выбором.

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

  3. Есть сильное ощущение, что Rust сейчас пользуется тем, что это новый язык, без груза совместимости с чем-то еще (как у C++ с C) и без необходимости поддерживать туеву хучу легаси кода. Поэтому в Rust-е все можно сразу сделать хорошо и "правильно". Отсюда и появляются нештяки, вроде встроенных в язык АлгТД, модулей и системы Cargo, единственного компилятора для всех платформ и пр.

    Пытаясь сопоставить C++ и Rust понимаешь, что не смотря на то, что в C++ можно обеспечить довольно таки высокий уровень безопасности кода (смотрим хелперы, вроде not_null и иже с ними), все-таки без серьезной модификации языка в C++ не получится завести те же самые АлгТД, а так же избавиться от таких вещей, как возможность вернуть ссылку на временный объект, который будет разрушен после возврата. Здесь Rust находится в заведомо лучшей позиции, чем C++.

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

    let a = ...;
    let b = a;

    приводит к тому, что содержимое a переносится в b. Но не всегда. Если a принадлежит примитивному типу или типу, реализующему трейт Copy, то произойдет копирование. Т.е. в каких-то случаях после b=a значением a пользоваться нельзя и компилятор будет бить за это по рукам. А в каких-то случая -- можно. Имхо, такая неоднозначность не есть хорошо, в частности для обобщенного программирования.

    Отдельным вопросом, о который уже сломано множество копий, является подход к обработке ошибок в Rust-а. Типа, везде должен возвращаться Result. Но и паники есть. Поэтому тем, кто озабочен написанием повторно-используемых библиотек, которые будут использоваться хрен знает в каких обстоятельствах, обеспечению exception safety (или правильно называть panic safety?) в Rust-е придется уделять столько же внимания, как и в C++. Но при этом еще и нужно будет долбаться с протягиванием кодов ошибок. Т.е. придется еще и писать код в духе "без try! ни строчки", как, например, это показывается в стандартной документации Rust-а:

    let f = try!(File::create("foo.txt"));
    let metadata = try!(f.metadata());
    let permissions = metadata.permissions();
    println!("permissions: {}", permissions.mode());

    Правда, в намедни вышедшем Rust 1.13 уже можно заменить try! вопросиками:

    let f = File::create("foo.txt")?;
    let metadata = f.metadata()?;
    let permissions = metadata.permissions();
    println!("permissions: {}", permissions.mode());

    Но тут мы опять приходим к тому, что в Rust-е имеет значение каждая закорючка в коде.

    Опять же, возьмем обобщенное программирование, в котором в Rust-е можно назначать аргументам шаблонов требования -- какие трейты должен поддерживать шаблон. Это сильно напоминает уже готовые concept-ы, которые в C++ все никак не могут завезти. Отсутствие ограничений на параметры шаблонов в C++ приводят к более сложной диагностики ошибок. Но, при этом, в C++ действительно возможен duck typing в compile-time: ну буквально, если шаблон ожидает "утку" в качестве параметра, то ему можно подсунуть все, что угодно, если это хоть сколько-нибудь похоже на "утку". И в этом огромная сила C++ных шаблонов и причина успеха шаблонов в C++. А вот в Rust-е, если шаблон ждет "утку", то и подсовывать ему нужно будет именно, что "утку". Может для тех, кто переходит на Rust с Java или C# это кажется нормальным, а вот мне это кажется очередным ограничением. Очередной попыткой ударить разработчика по рукам (см. пункт №2 выше).

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

  4. В самом начале книги "Why Rust" автор сделал заход, который мне показался жульническим и из-за которого я ко всему остальному материалу относился с изрядной долей скепсиса. Речь о наезде на понятие UB (undefined behavior) в языках C и C++. Мол, если вы в C/C++ обратитесь за пределы массива, то возникнет UB. А вот в Rust-е, мол, никакого UB -- вы получите вполне себе панику в run-time.

    Как по мне, так странный заход. Из категории, "мой дедушка боролся за то, чтобы не было бедных, а не за то, чтобы не было богатых". Типа того, что у программиста руки все-равно кривые, поэтому мы не будем заморачиваться тем, как вообще сделать невозможными выходы за пределы буфера. Мы просто сделаем контроль в run-time. А за счет того, что у нас есть итераторы, а так же компилятор умный, мы большинство таких проверок выбросим и вы даже никаких дополнительных накладных расходов не получите.

  5. Есть у меня подозрение, что подход разработчиков Rust-а можно выразить так: "Мы подумали о том, что вам может пригодится, а что не может, и дали вам то, чем вам стоит пользоваться. Этого вам хватит на все времена." Только вот я не уверен, что они [разработчики Rust] правы в своих убеждениях. Это касается разных вещей в языке. Начиная от необходимости ухода в unsafe для написания банального двусвязного списка, и заканчивая подходом к обеспечению thread safety. Так, есть у меня подозрения, что некоторые C++ные навороты, к которым приходится прибегать при работе с реальными проектами, в принципе возможны в Rust. Посему, если в реальной жизни придется столкнуться с чем-то нетривиальным (вроде реализации своего many-readers/single-writer spin-lock-а), то скорее всего с Rust-ом придется бороться, т.е. его сильные стороны могут начать работать против разработчиков.

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

воскресенье, 13 ноября 2016 г.

[business] Ищу примеры документов "Technical Assistance Agreement" и/или "Support Service Agreement"

Знаю, что некоторые компании бесплатно выпускают OpenSource-продукты под пермиссивными лицензиями (BSD, MIT, Boost и пр.), а продают техподдержку и консультации. Например, есть бесплатная библиотека POCO (не самая плохая, кстати говоря) и есть компания Applied Informatics, которая стоит за POCO и которая продает техподдержку пользователям POCO. Текст соглашения о такой техподдержке можно найти в виде PDF-ки на сайте Applied Informatics.

Поскольку мы выпускали, выпускаем и будем выпускать SObjectizer под BSD-лицензией, которая разрешает бесплатно использовать SObjectizer в каких угодно проектах, хоть открытых, хоть закрытых, то есть мысль попробовать предгалать платную техподдержку для SObjectizer. Для того, чтобы понять, как это все оформить, хочется ознакомиться как можно с большим количеством документов типа "Technical Assistance Agreement", "Support Service Agreement" и тому подобных. Но документов, касающихся техподдержки именно открытого, бесплатного ПО.

Буду признателен за ссылочки на подобные документы, которыми читатели поделятся в комментариях. Заранее спасибо!