C++, Maybe
Dec. 23rd, 2012 08:46 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Колись, якщо я не помиляюсь,
ivan_gandhi писав що методи get це зло. На жаль, я того посту знайти не можу. Пам’ятаю тільки що у коментарях хтось як альтернативу запропонував методи with. Я тоді не звернув увагу.
А два тижні тому у п’ятницю їхав з роботи і надумав собі що це ж класна штука насправді! От уявіть собі вказівник: він може на когось указувати, а може і не указувати. Така його природа. І коли він на когось указує — з ним можна працювати. А коли не указує — не можна. І треба постійно втикати if (foo != nullptr) і подібні штуки.
Ну, raw-pointer це взагалі штука небезпечна. У пристойному суспільстві треба його використання добряче обґрунтувати, а то і канделябром по пальцям отримати не довго. Зараз модно замість raw-pointers використовувати smart-pointers. Про це сам Stroustrup каже.
Так от, „розумні“ вказівники. Оператор розіменування (ідіотська назва, як на мене) чи „стрілка“ — це і є ті самі „геттери“. В чому їхня проблема? Та у тому що коли вказівник пустий то невідомо що робити з тим геттером. Ось три погані варіанти:
Чим поганий перший, я думаю, зрозуміло без додаткових коментарів. Ну серйозно, кому сподобається софт з undefined behaviour всередині?
Exception поганий тим що він не є частиною типу функції у C++. Його розповсюдження дуже важко контролювати. Зараз, кажуть, з noexcept стало краще, але я ще його толком не пробував. А що буває з неловленим exception знають усі хто їх хоч раз використовував. Terminate, жахливий і неминучий.
Default value. Цей підхід поганий тим що не у всіх типів буває default value.
На відміну від цих „тупих відмазок“, як мені здається, with вирішує проблему. Дивіться, метод with приймає на вхід функцію у яку треба передати „внутрішнє“ значення. Я маю на увазі, значення на яке вказівник указує. Нема значення — не передаємо. Якщо результат функції void то взагалі ніяких проблем. Якщо тип то тут уже складніше — у нас не буває вказівників на r-value.
Подібний підхід можна застосовувати для масивів щоб примусово перевірити індекс. І для асоціативних масивів. Може, ще для чогось.
Щоб перевірити концепт я вирішив „потренуватись на кішках“ — реалізувати Maybe. Перша спроба була невдала. Але тут з усіх сторін посипались статті і пости про те як можна union і placement new використати для всяких різних фантастичних штук. Ну, там, optional<t> і все таке. Кажуть, цей тип буде у C++14. Та і Сам Александреску там писав про роботу з помилками і тип Either на базі цього трюку. Воістину — свідомість змінює реальність!
Що ж, я не довго вагався і швиденько накалякав маленький PoC. Enjoy:
Я хочу звернути вашу увагу на те що b1 має тип Maybe<double>, але без проблем працює з print що приймає на вхід int.
Конструювання трошки коряве. Тип можна було б виводити автоматично із аргументу, тіпа make_just(123). Але я хотів зробити можливим неявне конструювання: Maybe<Foo>::just(10). І тут вивести тип уже не вийде.
Крім того стиль починає нагадувати CPS, що теж не додає привабливості.
Цікаво ще те що with це, фактично, map + apply. Можна було б залишити тільки map і повертати std::function чи, там, лямбду і погратись з функторами. А можна було б замінити with на operator>> і oparetor>>= і погратись у монади.
Не знаю, не знаю...
![[livejournal.com profile]](https://www.dreamwidth.org/img/external/lj-userinfo.gif)
А два тижні тому у п’ятницю їхав з роботи і надумав собі що це ж класна штука насправді! От уявіть собі вказівник: він може на когось указувати, а може і не указувати. Така його природа. І коли він на когось указує — з ним можна працювати. А коли не указує — не можна. І треба постійно втикати if (foo != nullptr) і подібні штуки.
Ну, raw-pointer це взагалі штука небезпечна. У пристойному суспільстві треба його використання добряче обґрунтувати, а то і канделябром по пальцям отримати не довго. Зараз модно замість raw-pointers використовувати smart-pointers. Про це сам Stroustrup каже.
Так от, „розумні“ вказівники. Оператор розіменування (ідіотська назва, як на мене) чи „стрілка“ — це і є ті самі „геттери“. В чому їхня проблема? Та у тому що коли вказівник пустий то невідомо що робити з тим геттером. Ось три погані варіанти:
- undefined behaviour;
- exception;
- default value.
Чим поганий перший, я думаю, зрозуміло без додаткових коментарів. Ну серйозно, кому сподобається софт з undefined behaviour всередині?
Exception поганий тим що він не є частиною типу функції у C++. Його розповсюдження дуже важко контролювати. Зараз, кажуть, з noexcept стало краще, але я ще його толком не пробував. А що буває з неловленим exception знають усі хто їх хоч раз використовував. Terminate, жахливий і неминучий.
Default value. Цей підхід поганий тим що не у всіх типів буває default value.
На відміну від цих „тупих відмазок“, як мені здається, with вирішує проблему. Дивіться, метод with приймає на вхід функцію у яку треба передати „внутрішнє“ значення. Я маю на увазі, значення на яке вказівник указує. Нема значення — не передаємо. Якщо результат функції void то взагалі ніяких проблем. Якщо тип то тут уже складніше — у нас не буває вказівників на r-value.
Подібний підхід можна застосовувати для масивів щоб примусово перевірити індекс. І для асоціативних масивів. Може, ще для чогось.
Щоб перевірити концепт я вирішив „потренуватись на кішках“ — реалізувати Maybe. Перша спроба була невдала. Але тут з усіх сторін посипались статті і пости про те як можна union і placement new використати для всяких різних фантастичних штук. Ну, там, optional<t> і все таке. Кажуть, цей тип буде у C++14. Та і Сам Александреску там писав про роботу з помилками і тип Either на базі цього трюку. Воістину — свідомість змінює реальність!
Що ж, я не довго вагався і швиденько накалякав маленький PoC. Enjoy:
#include <cmath> #include <iostream> #include <utility> #include <type_traits> template <typename T> class Maybe { public: typedef T value_type; template <typename F> auto with(const F& func) -> Maybe<decltype(func(std::declval<T>()))> { if (m_just) return Maybe<decltype(func(m_value))>::just(func(m_value)); return Maybe<decltype(func(m_value))>::nothing(); } template <typename VT> void with(void (&func) (VT)) { if (m_just) func(m_value); } template <typename VT> static Maybe<T> just(VT&& v) { return Maybe<T>(std::forward<VT>(v)); } static Maybe<T> nothing() { return Maybe<T>(); } private: union { T m_value; bool m_placeholder; }; bool m_just; template <typename VT> Maybe(VT&& v) : m_value(std::forward<VT>(v)), m_just(true) {} Maybe() : m_placeholder(false), m_just(false) {} }; void print(int v) { std::cout << "v = " << v << std::endl; } int main() { auto a = Maybe<int>::nothing(); auto b = Maybe<int>::just(10); auto a1 = a.with(sqrt); auto b1 = b.with(sqrt); a1.with(print); b1.with(print); return 0; } |
_Winnie C++ Colorizer |
Я хочу звернути вашу увагу на те що b1 має тип Maybe<double>, але без проблем працює з print що приймає на вхід int.
Конструювання трошки коряве. Тип можна було б виводити автоматично із аргументу, тіпа make_just(123). Але я хотів зробити можливим неявне конструювання: Maybe<Foo>::just(10). І тут вивести тип уже не вийде.
Крім того стиль починає нагадувати CPS, що теж не додає привабливості.
Цікаво ще те що with це, фактично, map + apply. Можна було б залишити тільки map і повертати std::function чи, там, лямбду і погратись з функторами. А можна було б замінити with на operator>> і oparetor>>= і погратись у монади.
Не знаю, не знаю...