Skip to content

Instantly share code, notes, and snippets.

@Siapran
Created March 4, 2020 15:33
Show Gist options
  • Save Siapran/6e6a87ac5043e13a89bbb289dad4aacf to your computer and use it in GitHub Desktop.
Save Siapran/6e6a87ac5043e13a89bbb289dad4aacf to your computer and use it in GitHub Desktop.
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