Share
Embed
Transcript
31 slides
-
おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.
harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について -
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(); } } -
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だけですか? -
harmonyの動機付け • std::optionalのnullチェックを隠蔽したい! • コードの可読性の向上 • 条件記述ミスやチェック忘れの回避 • 便利な意味論を持つ、モナド的操作をしたい!! •
コード可読性の向上 • 条件記述ミスを減らす • 同じことを同じように書きたい!!! • 異なる型に対して同じ操作で同じことを行いたい • コードの可読性の向上 • ライブラリの利便性向上 -
おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.
harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について -
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; } -
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; } -
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 の様な変換が可能なほか、無 効値(有効値)から有効値 (無効値)の様な変換が可能 • 変換先の型は変換した値とし ない値両方を表現できなけれ ばならない -
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; } -
Monadic Interface – その他色々 • try_catch • 例外->eitherモナドへの変換 • map_to
• 値への直接変換 • fold_to • 有効値と無効値を集約しつつ変換する • value_or • 有効値を取り出し、なければ代わりの値を返す • harmonize • モナド的とみなせない型をeitherモナド的に扱えるようにする • 例えばbool型でmatchとかできるようにする -
harmony • Githubに公開しています • onihusube/harmony: C++ Monadologie (github.com) • ヘッダオンリー
• 要C++20コンパイラ • GCC 10.3/11.1 • MSVC 2019 latest/MSVC 2022 • Clang 12.0? -
おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.
harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について -
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はこれらの型をコンセプトを通して扱うことで、型の持つ 意味論の一部にのみ着目し統一的に扱う -
harmonyの対象とコンセプトの対応 • unwrappable コンセプト • maybe コンセプト • either コンセプト
result<T, E> expected<T, E> variant<L, R> • list コンセプト rangeコンセプトを満たすもの T* unique_ptr<T> optional<T> future<T> -
おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.
harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について -
コンセプト - unwrappable template<typename T> concept unwrappable = requires(T&& m)
{ { cpo::unwrap(std::forward<T>(m)) } -> not_void; }; • unwrap CPOによって値を取得可能であること! • そして、結果がvoidではないこと -
CPO? • CPO = Customization Point Object • いくつものカスタマイゼーションポイントの集積点となる関数 オブジェクト
• カスタマイゼーションポイントにまつわるいくつもの問題に対 処した、C++20以降必修のイディオム • 同じ意味論に対応する操作が複数存在しているとき、それをま とめ上げるのにCPOを利用できる -
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> -
unwrappable 再掲 template<typename T> concept unwrappable = requires(T&& m) {
{ cpo::unwrap(std::forward<T>(m)) } -> not_void; }; • この1つの制約式には、少なくとも6つの操作に対する要求が含 まれている • このコンセプトを使用する所ではunwrap CPOを利用すること で、統一的な意味論の下で複雑な手続きなしに6種の操作にア クセスできる -
CPO -> コンセプト • コンセプト定義においては、CPOを利用することで対応する複 数の操作に関する制約をシンプルに表現できる • そのコンセプトを利用する所では、そのCPOを用いることでコ ンセプトの枠内で型に応じた最適な操作を自動で選択できる •
CPOによる操作の統一とそれを用いたコンセプト定義は、 C++20 Rangeライブラリのrangeコンセプトにも見ることがで きる -
コンセプト - maybe template<typename T> concept maybe = unwrappable<T> and
requires(const T& m) { { cpo::validate(m) } -> std::same_as<bool>; }; • unwrappableかつ、validate CPOによって有効性(内包する値の有無) を取得可能であること! -
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> -
コンセプト - 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によって無効値を取得可能で あること! -
CPO – unwrap_other • unwrap_other CPOは次のいずれかの手段によって型の内包す る無効値(あるいはもう片方の値)を取得する • std::nullopt :
std::optional • nullptr : ポインタ型・スマートポインタ型 • .error() : std::expected • .unwrap_err() :在野のresult/expected-likeな型 • get<0>() : variant<L, R> -
|の簡易実装例 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; } }; -
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))); } } }; -
おしながき 1. harmony について 1. 動機付け 2. harmonyのMonadic Interface 2.
harmonyのコンセプト、その実装 1. harmonyの対象とコンセプト 2. harmonyのコンセプト、実装 3. コンセプトベースの設計について -
コンセプトベースライブラリデザイン • まずライブラリ対象についてをコンセプトを用いて定義する • コンセプトによってある概念に対応する型の性質を記述する • ライブラリの処理はコンセプトにのみ依存して記述する • 型の具体性に依存しないためジェネリックに書ける •
想定している具体的な対象以外に、幅広い型にライブラリを開放でき る • 型の具体性に踏み込む場合、それをもコンセプトで表現する • コンセプトの包含関係を意識してコンセプトを定義し それを用いて処理を特殊化すれば、特殊化対象を自動的に絞り込める -
コンセプトベースライブラリデザイン • やっていることは、数十年前から言われているSOLID原則とか その辺の抽象設計のおはなし • たとえば、原初のSTLにも見ることができる • C++20からはコンセプトを中心とした設計によって、それらの 原則をこのように書くことができる •
これらの設計は、Rangeライブラリ及びExecutorライブラリ (提案中)に特に見ることができる