Upgrade to Pro — share decks privately, control downloads, hide ads and more …

非侵入的モナド的操作導入ライブラリ harmony とコンセプト・CPO

onihusube
August 09, 2021

非侵入的モナド的操作導入ライブラリ harmony とコンセプト・CPO

talk.cpp スライド

onihusube

August 09, 2021
Tweet

More Decks by onihusube

Other Decks in Programming

Transcript

  1. おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.

    harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について
  2. harmonyの動機1 • どこからかもらってきた std::optional • ifチェックだるくないです か? • ネストすると見辛い •

    連続しても見辛い • 忘れたり間違えたり・・・ auto f() -> std::optional<int>; void success(int&); void failure(); int main () { auto opt = f(); if (opt) { success(*opt); } else { failure(); } }
  3. harmonyの動機2 auto f() -> std::optional<int>; // ポインタ型 auto g() ->

    int*; int main () { // こう書けたとしたら f() | and_then([](int&) { ... }) | and_then([](int&) { ... }) | or_else([]() { ... }); // こう書きたくないですか? g() | and_then([](int&) { ... }) | and_then([](int&) { ... }) | or_else([]() { ... }); } auto f() -> std::optional<int>; // std::expected auto g() -> std::expected<int, std::errc>; int main () { // expectedでも同じように書きたくないですか? g() | and_then([](int&) { ... }) | and_then([](int&) { ... }) | or_else([](std::errc) { ... }); } • モナド的に扱いたいのはstd::optionalだけですか?
  4. harmonyの動機付け • std::optionalのnullチェックを隠蔽したい! • コードの可読性の向上 • 条件記述ミスやチェック忘れの回避 • 便利な意味論を持つ、モナド的操作をしたい!! •

    コード可読性の向上 • 条件記述ミスを減らす • 同じことを同じように書きたい!!! • 異なる型に対して同じ操作で同じことを行いたい • コードの可読性の向上 • ライブラリの利便性向上
  5. おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.

    harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について
  6. monas | func • harmony::monasで包み|で後続処 理を接続する • monasはラッパ型、`|`による パイプを提供する •

    optionalに対して|で接続された 処理は、有効値を保持している場 合にのみ実行される • monas | funcの結果は、また monasに写される #include "harmony.hpp" auto f() -> std::optional<int>; auto func1(int&) -> std::optional<int>; auto func2(int&) -> int; auto func3(int&) -> void; int main() { harmony::monas(f()) | func1 | func2 | func3; }
  7. Monadic Interface - map/map_err • mapは有効値を変換し、 map_errは無効値を変換する • 変換は指定された処理(関数) によって行われる

    • それぞれ、有効値を保持して いる時/無効値を保持してい る時にしか変換しない • 結果はまたmonasに写される auto to_string(int&) -> std::string; auto error(std::nullopt_t) -> int; auto func1(std::string_view) -> void; auto func2(int) -> void; int main () { using namespace harmony::monadic_op; harmony::monas(f()) | map(to_string) // int -> std::string | func1; harmony::monas(f()) | map_err(error) // std::nullopt_t -> int | func2; }
  8. Monadic Interface - and_then/or_else auto to_string(int&) -> std::optional<std::string>; auto error(std::nullopt_t)

    -> std::optional<int>; auto func1(std::string_view) -> void; auto func2(int) -> void; int main () { harmony::monas(f()) | and_then(to_string) | func1; harmony::monas(f()) | or_else(error) | func2; } • and_then/or_elseは値を包 む型ごと変換を行う • 例えば、optional->expected の様な変換が可能なほか、無 効値(有効値)から有効値 (無効値)の様な変換が可能 • 変換先の型は変換した値とし ない値両方を表現できなけれ ばならない
  9. Monadic Interface - match • matchは有効値と無効値両方 に対する処理を指定する • 戻り値は1つの型に集約され た値

    • 有効値と無効値に対する処理結 果の間にcommon_typeが必要 • そのcommon_typeがモナド的型 であるとき、結果はまたmonas に写される • これさえあれば他いらない説 がある、とても便利 auto f() -> std::optional<int>; auto to_string(int&) -> std::string; auto error(std::nullopt_t) -> std::string; auto func(std::string_view) -> void; int main () { harmony::monas(f()) | match(to_string, error) | func; }
  10. Monadic Interface – その他色々 • try_catch • 例外->eitherモナドへの変換 • map_to

    • 値への直接変換 • fold_to • 有効値と無効値を集約しつつ変換する • value_or • 有効値を取り出し、なければ代わりの値を返す • harmonize • モナド的とみなせない型をeitherモナド的に扱えるようにする • 例えばbool型でmatchとかできるようにする
  11. harmony • Githubに公開しています • onihusube/harmony: C++ Monadologie (github.com) • ヘッダオンリー

    • 要C++20コンパイラ • GCC 10.3/11.1 • MSVC 2019 latest/MSVC 2022 • Clang 12.0?
  12. おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.

    harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について
  13. harmonyの対象 • harmonyの対象はstd::optionalだけではない • std::optional • ポインタ型/スマートポインタ型 • コンテナ型(range) •

    巷のresult-likeな型 • boost::outcome/boost::outcome::resultとか • std::expected (C++23?) • std::future/std::shared_future • boostにある同等のものも • std::error_code • ただし、意味論が他と逆 • その他… • Harmonyはこれらの型をコンセプトを通して扱うことで、型の持つ 意味論の一部にのみ着目し統一的に扱う
  14. harmonyの対象とコンセプトの対応 • unwrappable コンセプト • maybe コンセプト • either コンセプト

     result<T, E>  expected<T, E>  variant<L, R> • list コンセプト  rangeコンセプトを満たすもの T* unique_ptr<T> optional<T> future<T>
  15. おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.

    harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について
  16. コンセプト - unwrappable template<typename T> concept unwrappable = requires(T&& m)

    { { cpo::unwrap(std::forward<T>(m)) } -> not_void; }; • unwrap CPOによって値を取得可能であること! • そして、結果がvoidではないこと
  17. CPO? • CPO = Customization Point Object • いくつものカスタマイゼーションポイントの集積点となる関数 オブジェクト

    • カスタマイゼーションポイントにまつわるいくつもの問題に対 処した、C++20以降必修のイディオム • 同じ意味論に対応する操作が複数存在しているとき、それをま とめ上げるのにCPOを利用できる
  18. CPO - unwrap • unwrap CPOは次のいずれかの手段によって型の内包する値を 取り出す 1. oprator* :

    ポインタ型、std::optional等 2. .value() : std::expected<T, E> 3. .unwrap() : 在野のresult/expected-likeな型 4. std::views::all() : range 5. get<1>() : variant<L, R> 6. .get() : future<T>
  19. unwrappable 再掲 template<typename T> concept unwrappable = requires(T&& m) {

    { cpo::unwrap(std::forward<T>(m)) } -> not_void; }; • この1つの制約式には、少なくとも6つの操作に対する要求が含 まれている • このコンセプトを使用する所ではunwrap CPOを利用すること で、統一的な意味論の下で複雑な手続きなしに6種の操作にア クセスできる
  20. コンセプト - maybe template<typename T> concept maybe = unwrappable<T> and

    requires(const T& m) { { cpo::validate(m) } -> std::same_as<bool>; }; • unwrappableかつ、validate CPOによって有効性(内包する値の有無) を取得可能であること!
  21. CPO - validate • validate CPO (いいお名前募集中)は次のいずれかの手段に よって型の内包する値の存在を判定する • boolへの明示的変換

    : ポインタ型、std::optional等 • .has_value() : std::expected等 • .is_ok() : 在野のresult/expected-likeな型 • std::ranges::empty() : range • .index() == 1 : variant<L, R> • .valid() : future<T>
  22. コンセプト - either template<typename T> concept either = maybe<T> and

    requires(T&& t) { {cpo::unwrap_other(std::forward<T>(t))} -> not_void; }; • maybeかつ、unwrap_other CPOによって無効値を取得可能で あること!
  23. CPO – unwrap_other • unwrap_other CPOは次のいずれかの手段によって型の内包す る無効値(あるいはもう片方の値)を取得する • std::nullopt :

    std::optional • nullptr : ポインタ型・スマートポインタ型 • .error() : std::expected • .unwrap_err() :在野のresult/expected-likeな型 • get<0>() : variant<L, R>
  24. |の簡易実装例 tempalte<typename M> class monas { M m_monad; public: template<typename

    F> friend constexpr auto operator|(monas& self, F&& f) -> monas<T>& { self.m_monad = f(cpo::unwrap(self.m_monad)); return self; } template<typename F> requires maybe<M> friend constexpr auto operator|(monas& self, F&& f) -> monas<T>& { if (cpo::validate(self.m_monad)) { self.m_monad = f(cpo::unwrap(self.m_monad)); } return self; } };
  25. mapの簡易実装例 template<typename T> struct map_impl { T fmap; template<unwrappable M>

    friend constexpr auto operator|(M&& m, map_impl self) { return monas(self.fmap(cpo::unwrap(std::forward<M>(m)))); } template<either M> friend constexpr auto operator|(M&& m, map_impl self) { using R = /* 変換後の型を取得 */; if (cpo::validate(m)) { return monas<R>(self.fmap(cpo::unwrap(std::forward<M>(m)))); } else { // 無効値の変換処理 return monas<R>(cpo::unwrap_other(std::forward<M>(m))); } } };
  26. おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.

    harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について
  27. コンセプトベースライブラリデザイン • まずライブラリ対象についてをコンセプトを用いて定義する • コンセプトによってある概念に対応する型の性質を記述する • ライブラリの処理はコンセプトにのみ依存して記述する • 型の具体性に依存しないためジェネリックに書ける •

    想定している具体的な対象以外に、幅広い型にライブラリを開放でき る • 型の具体性に踏み込む場合、それをもコンセプトで表現する • コンセプトの包含関係を意識してコンセプトを定義し それを用いて処理を特殊化すれば、特殊化対象を自動的に絞り込める