madf: (Default)
[personal profile] madf
Bartosz Milewski підняв цікаву тему: як функтори, аплікативні функтори і монади допомагають боротися з інверсією управління при використанні асинхронного API (в контексті C++).
Відео: „Вступ і про функтори“, „Аплікативні функтори“, „Монади“.
Слайди: https://www.fpcomplete.com/wp-content/uploads/2012/09/Functional-Patterns.pdf

Я ще в середу подивився слайди на роботі, а сьогодні переглянув відео. Все б нічого, чувак правильні речі каже. Але проблема в тому що каже він багато і заплутано. Особливо з нестандартними картинками у яких він, здається, сам заплутався. Для людини знайомої з поняттями функтора і монади все зрозуміло, але ж і користі ніякої. А для середньостатистичного погроміста на C++ нічого, я думаю, не зрозуміло і знову таки — користі ніякої.

Особливо прикольно було чути: „Моноїд — це група без зворотнього елемента“. Чувак! Ці люди не знають хто такий моноїд! Звідки їм знати про групу?! Ну добре, він розказав про функтори і монади і показав як у Haskell все це кльово виглядає. Але перше ж запитання яке виникне у, знову таки, середньостатистичного плюсатого програміста буде: „Зашибись! А нафіга нам все це?“. Особливо після того як Bartosz зізнається що у C++ реалізація цих паттернів виглядає жахливо.

І це прекрасно підтверджується коментарями на isocpp.org: „О, чувак каже про andThen! А у нас тут як раз є N3634 з std::future::then(). Зашибись, все вже вкрадено до нас!“. Хоча очевидно що мова йде не про std::future (і сам Bartosz про це каже) а про концепцію взагалі. От тільки донести її у нього не вийшло.

Знаєте, є люди що вміють розказувати історії. У них добре виходить робити доповіді — навіть без слайдів, або самими тільки слайдами. А є такі що не вміють розказувати (до речі, я відношусь саме до них) — вони викладуть кілька сухих фактів, які не будуть „склеєні“, не будуть „вплетені“ у нитку розповіді. Таких слухати просто нецікаво. У мене є товариш який може ну абсолютно будь-який факт подати так що заслухаєшся! Я критикую Bartosz'а, але сам би на його місці сфейлив би так само.

Між іншим, тут уже не один місяць намагаються просунути у стандарт новий тип std::optional (нащадок boost::optional, а-ля Maybe із Haskell) — N3672. І я не можу зрозуміти чому вони не додадуть до інтефейсу цього класу метод map (чи fmap). А ще краще най це буде вільна функція, спеціалізована для контейнерів, std::optional, std::future, whatever. Цитую:

Accessing the contained value

It was chosen to use indirection operator because, along with explicit conversion to bool, it is a very common pattern for accessing a value that might not be there:

if (p) use(*p);

This pattern is used for all sort of pointers (smart or dumb), and it clearly indicates the fact that the value may be missing and that we return a reference rather than a value. The indirection operator has risen some objections because it may incorrectly imply that optional is a (possibly smart) pointer, and thus provides shallow copy and comparison semantics. All library components so far use indirection operator to return an object that is not part of the pointer's/iterator's value. In contrast, optional indirects to the part of its own state. We do not consider it a problem in the design; it is more like an unprecedented usage of indirection operator. We believe that the cost of potential confusion is overweighed by the benefit of an easy to grasp and intuitive interface for accessing the contained value.

По-моєму, всі ці пляски з бубном навколо „розіменування“ і перевірки на „непустоту“ всім давно обридли, а використання функціонального підходу довело свою простоту, корисність і правильність. Те що вони пропонують не підтримує композиції, а без композиції будуть старі-добрі дерева if-ів. Або повне нехтування перевірками і, як результат, або undefined beahviour або адъ exceptions (після якого хтось знову накладе мораторій на них). Ну скільки ж можна наступати на одні й ті ж граблі? Зробіть уже нормальний інтерфейс функтора, а ще краще — монади!

Взагалі, якщо чесно, хоча C++11 суттєво еволюціонував, і додав багато можливостей для використання функціонального стилю, дрібні „болячки“ уже порядком дістали. Буквально вчора на роботі боровся з тим що у C++ неможливо у compile-time визначити кількість елементів у списку ініціалізації. Я хотів зробити „тіпа dependent type“ такого виду:

template <size_t N>
class Table {
    public:
        Table(const std::string& name, <something-that-holds-field-names>);

        tamplate <typename ... Ts>
        void addRow(const Ts& ... values)
        {
            static_assert(N == sizeof...(values), "The number of fields and the number of values should be the same.");
            // ...
        }
};

template <size_t N>
Table<N> makeTable(const std::string& name, <something-that-holds-field-names>)
{
    return Table<N>(name, <something-that-holds-field-names>)
}


makeTable — стандартний „фокус“ для автоматичного виведення типів (бо C++ вміє виводити типи тільки з аргументів). Але я не зміг придумати нічого для передачі списка полів. Ідеально підійшов би std::array, але у нього немає конструктора для списку ініціалізації. Прекрасно підійшов би std::initializer_list, але його методи не є constexpr і недоступні у compile-time (абсолютно незрозуміло чому). Криво-косо, але підійшов би C-style масив, але у функцію він передається без розмірності і визначити його розмір, знову таки, неможливо. Єдине що підходить — variadic template, але виглядати це буде... коряво:

auto table = makeTable("MyTable", "foo", "bar", "baz");


Хотілося б згрупувати поля окремо.

Мабуть пора кидати C++ і серйозно братись за Haskell. Правда там, на скільки я знаю, цю проблему теж вирішити не вийде. Але принаймні інструмент приємний.

Profile

madf: (Default)
madf

April 2018

S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 7th, 2026 06:53 pm
Powered by Dreamwidth Studios