Created
March 4, 2020 15:33
-
-
Save Siapran/6e6a87ac5043e13a89bbb289dad4aacf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace detail { | |
template <typename T> struct is_optional : std::false_type {}; | |
template <typename T> struct is_optional<std::optional<T>> : std::true_type {}; | |
template <typename T> | |
[[maybe_unused]] constexpr bool is_optional_v = is_optional<std::decay_t<T>>::value; | |
/** | |
* // I hate this | |
* | |
* void foo(std::optional<std::string> bar) {} | |
* void foo(std::string bar) {} | |
* | |
* int main() { | |
* foo("bar"); // ambiguous call... | |
* return 0; | |
* } | |
*/ | |
template <typename T> | |
struct coerce_opt { | |
// this is here to avoid constructing optionals when we don't need to | |
template <typename FnVal, typename FnNil> | |
decltype(auto) operator()(T &&t, FnVal &&fval, FnNil &&) { | |
return std::invoke(std::forward<FnVal>(fval), std::move(t)); | |
} | |
template <typename FnVal, typename FnNil> | |
decltype(auto) operator()(T const &t, FnVal &&fval, FnNil &&) { | |
return std::invoke(std::forward<FnVal>(fval), t); | |
} | |
template <typename FnVal, typename FnNil> | |
decltype(auto) operator()(std::optional<T> &&t, FnVal &&fval, FnNil &&fnil) { | |
if (t.has_value()) { | |
return std::invoke(std::forward<FnVal>(fval), std::move(t).value()); | |
} else { | |
return std::invoke(std::forward<FnNil>(fnil)); | |
} | |
} | |
template <typename FnVal, typename FnNil> | |
decltype(auto) operator()(std::optional<T> const &t, FnVal &&fval, FnNil &&fnil) { | |
if (t.has_value()) { | |
return std::invoke(std::forward<FnVal>(fval), t.value()); | |
} else { | |
return std::invoke(std::forward<FnNil>(fnil)); | |
} | |
} | |
}; | |
} // namespace detail | |
template <typename Key> | |
auto at(Key key) { | |
return zug::comp([key](auto&& functor) { | |
return [functor, &key](auto&& whole) { | |
using Part = std::decay_t<decltype(whole.at(key))>; | |
return functor([&]() -> std::optional<Part> { | |
try { | |
return LAGER_FWD(whole).at(key); | |
} catch (std::out_of_range const&) { return std::nullopt; } | |
}())([&](auto&& part) { | |
auto res = LAGER_FWD(whole); | |
return detail::coerce_opt<Part>{}( | |
LAGER_FWD(part), | |
[&](auto&& val) -> decltype(auto) { | |
try { | |
res.at(key) = LAGER_FWD(val); | |
} catch (std::out_of_range const&) {} | |
return res; | |
}, | |
[&]() -> decltype(auto) { return res; }); | |
}); | |
}; | |
}); | |
} | |
template <typename Key> | |
auto at_i(Key key) { | |
return zug::comp([key](auto&& functor) { | |
return [functor, &key](auto&& whole) { | |
using Part = std::decay_t<decltype(whole.at(key))>; | |
return functor([&]() -> std::optional<Part> { | |
try { | |
return LAGER_FWD(whole).at(key); | |
} catch (std::out_of_range const&) { return std::nullopt; } | |
}())([&](auto&& part) { | |
auto res = LAGER_FWD(whole); | |
return detail::coerce_opt<Part>{}( | |
LAGER_FWD(part), | |
[&](auto&& val) -> decltype(auto) { | |
if (static_cast<std::size_t>(key) < whole.size()) { | |
return std::move(res).set(key, LAGER_FWD(val)); | |
} else { | |
return std::move(res); | |
} | |
}, | |
[&]() -> decltype(auto) { return std::move(res); }); | |
}); | |
}; | |
}); | |
} | |
template <typename T> | |
auto fallback(T&& t) { | |
return zug::comp([t = std::forward<T>(t)](auto&& f) { | |
return [&, f](auto&& whole) { | |
using Whole = std::decay_t<decltype(whole)>; | |
using Part = std::decay_t<decltype(whole.value())>; | |
if (LAGER_FWD(whole).has_value()) { | |
return f(LAGER_FWD(whole).value())( | |
[&](auto&& x) { return Whole{LAGER_FWD(x)}; }); | |
} else { | |
return f(Part{t})([&](auto&& x) { | |
// allow writing back to the optional if not present | |
// something tells me we really just need | |
// std::expected<T, E> for optlift... | |
// return LAGER_FWD(whole); | |
return Whole{LAGER_FWD(x)}; | |
}); | |
} | |
}; | |
}); | |
} | |
/** | |
* @brief lift lenses to handle optionals | |
* optlift :: (lens<W, P> | lens<W, [P]>) -> lens<[W], [P]> | |
* @param lens | |
*/ | |
template <typename Lens> | |
auto optlift(Lens&& lens) { | |
return zug::comp([lens = std::forward<Lens>(lens)](auto&& f) { | |
return [&, f](auto&& whole) { | |
using Whole = std::decay_t<decltype(whole)>; | |
using Viewed = std::decay_t<decltype(::lager::view( | |
lens, std::declval<std::decay_t<decltype(whole.value())>>()))>; | |
using Part = std::optional<Viewed>; | |
if (LAGER_FWD(whole).has_value()) { | |
// forward the value to the lens | |
auto&& whole_val = LAGER_FWD(whole).value(); | |
return f(Part{ | |
::lager::view(lens, LAGER_FWD(whole_val))})([&](auto&& x) { | |
return detail::coerce_opt<Viewed>{}( | |
LAGER_FWD(x), | |
[&](auto&& val) { | |
return Whole{::lager::set( | |
lens, LAGER_FWD(whole_val), LAGER_FWD(val))}; | |
}, | |
[&]() { return LAGER_FWD(whole); }); | |
}); | |
} else { | |
// bypass the lens | |
return f(Part{std::nullopt})( | |
[&](auto&&) { return LAGER_FWD(whole); }); | |
} | |
}; | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment