// Copyright 2015, Tobias Hermann and the FunctionalPlus contributors. // https://github.com/Dobiasd/FunctionalPlus // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include #include #include #include #include #include #include #include namespace fplus { // Can hold a value of type T or nothing. template class maybe { public: bool is_just() const { return is_present_; } bool is_nothing() const { return !is_just(); } const T& unsafe_get_just() const { assert(is_just()); return *reinterpret_cast(&value_); } T& unsafe_get_just() { assert(is_just()); return *reinterpret_cast(&value_); } typedef T type; maybe() : is_present_(false), value_() {}; ~maybe() { destruct_content(); } maybe(const T& val_just) : is_present_(true), value_() { new (&value_) T(val_just); } maybe(T&& val_just) : is_present_(true), value_() { new (&value_) T(std::move(val_just)); } maybe(const maybe& other) : is_present_(other.is_just()), value_() { if (is_present_) { new (&value_) T(other.unsafe_get_just()); } } maybe(maybe&& other) : is_present_(std::move(other.is_present_)), value_() { if (is_present_) { new (&value_) T(std::move(other.unsafe_get_just())); } } maybe& operator = (const T& other) { destruct_content(); is_present_ = true; new (&value_) T(other); return *this; } maybe& operator = (T&& other) { destruct_content(); is_present_ = true; new (&value_) T(std::move(other)); return *this; } maybe& operator = (const maybe& other) { destruct_content(); if (other.is_just()) { is_present_ = true; new (&value_) T(other.unsafe_get_just()); } return *this; } maybe& operator = (maybe&& other) { destruct_content(); is_present_ = std::move(other.is_present_); if (is_present_) { new (&value_) T(std::move(other.unsafe_get_just())); } return *this; } private: void destruct_content() { if (is_present_) { is_present_ = false; (*reinterpret_cast(&value_)).~T(); } } bool is_present_; typename std::aligned_storage::type value_; }; namespace internal { template struct is_maybe : std::false_type { }; template struct is_maybe> : std::true_type { }; } // API search type: is_just : Maybe a -> Bool // fwd bind count: 0 // Is not nothing? template bool is_just(const maybe& maybe) { return maybe.is_just(); } // API search type: is_nothing : Maybe a -> Bool // fwd bind count: 0 // Has no value? template bool is_nothing(const maybe& maybe) { return !is_just(maybe); } // API search type: unsafe_get_just : Maybe a -> a // fwd bind count: 0 // Crashes if maybe is nothing! template T unsafe_get_just(const maybe& maybe) { return maybe.unsafe_get_just(); } // API search type: just_with_default : (a, Maybe a) -> a // fwd bind count: 0 // Get the value from a maybe or the default in case it is nothing. template T just_with_default(const T& defaultValue, const maybe& maybe) { if (is_just(maybe)) return unsafe_get_just(maybe); return defaultValue; } // API search type: throw_on_nothing : (e, Maybe a) -> a // fwd bind count: 1 // Throw exception if nothing. Return value if just. template T throw_on_nothing(const E& e, const maybe& maybe) { if (is_nothing(maybe)) throw e; return unsafe_get_just(maybe); } // API search type: just : a -> Maybe a // fwd bind count: 0 // Wrap a value in a Maybe as a Just. template maybe just(const T& val) { return val; } // API search type: as_just_if : ((a -> bool), a) -> Maybe a // fwd bind count: 1 // Wrap a value in a Maybe as a Just if the given predicate is fulfilled. // Otherwise a nothing is returned. template maybe as_just_if(Pred pred, const T& val) { internal::check_unary_predicate_for_type(); if (pred(val)) return val; else return {}; } // API search type: maybe_to_seq : Maybe a -> [a] // fwd bind count: 0 // Converts a maybe to a sequence. // singleton_seq(Just 3) == [3] // singleton_seq(Nothing) == [] template > ContainerOut maybe_to_seq(const maybe& maybe) { if (is_just(maybe)) return ContainerOut(1, unsafe_get_just(maybe)); return {}; } // API search type: singleton_seq_as_maybe : [a] -> Maybe a // fwd bind count: 0 // Converts a sequence to a maybe. // singleton_seq([]) == Nothing // singleton_seq([3]) == Just 3 // singleton_seq([3,4]) == Nothing template maybe singleton_seq_as_maybe(const Container& xs) { if (xs.size() == 1) return xs.front(); return {}; } // API search type: nothing : () -> Maybe a // Construct a nothing of a certain Maybe type. template maybe nothing() { return {}; } // True if just values are the same or if both are nothing. template bool operator == (const maybe& x, const maybe& y) { if (is_just(x) && is_just(y)) return unsafe_get_just(x) == unsafe_get_just(y); return is_just(x) == is_just(y); } // False if just values are the same or if both are nothing. template bool operator != (const maybe& x, const maybe& y) { return !(x == y); } // API search type: lift_maybe : ((a -> b), Maybe a) -> Maybe b // fwd bind count: 1 // Lifts a function into the maybe functor. // A function that for example was able to convert and int into a string, // now can convert a Maybe into a Maybe. // A nothing remains a nothing, regardless of the conversion. template auto lift_maybe(F f, const maybe& m) { internal::trigger_static_asserts(); using B = std::decay_t>; if (is_just(m)) return just(internal::invoke(f, unsafe_get_just(m))); return nothing(); } // API search type: lift_maybe_def : (b, (a -> b), Maybe a) -> b // fwd bind count: 2 // lift_maybe_def takes a default value and a function. // It returns a function taking a Maybe value. // This function returns the default value if the Maybe value is nothing. // Otherwise it applies the function to the value inside the Just // of the Maybe value and returns the result of this application. template auto lift_maybe_def(const Default& def, F f, const maybe& m) { internal::trigger_static_asserts(); using B = std::decay_t>; static_assert( std::is_convertible::value, "Default value must be convertible to Function's return type"); if (is_just(m)) return B(internal::invoke(f, unsafe_get_just(m))); return B(def); } // API search type: lift_maybe_2 : (((a, b) -> c), Maybe a, Maybe b) -> Maybe c // fwd bind count: 2 // Lifts a binary function into the maybe functor. // Applies the function only if both arguments are justs. // Otherwise returns a nothing. template auto lift_maybe_2(F f, const maybe& m_a, const maybe& m_b) { internal::trigger_static_asserts(); using FOut = std::decay_t>; if (is_just(m_a) && is_just(m_b)) { return just( internal::invoke(f, unsafe_get_just(m_a), unsafe_get_just(m_b))); } return nothing(); } // API search type: lift_maybe_2_def : (c, ((a, b) -> c), Maybe a, Maybe b) -> c // fwd bind count: 3 // lift_maybe_2_def takes a default value and a binary function. // It returns a function taking a two Maybe values. // This function returns the default value at least one of the // Maybe values is nothing. // Otherwise it applies the function to the two values inside the Justs // and returns the result of this application. template auto lift_maybe_2_def(const Default& def, F f, const maybe& m_a, const maybe& m_b) { internal::trigger_static_asserts(); using C = std::decay_t>; static_assert( std::is_convertible::value, "Default value must be convertible to Function's return type"); if (is_just(m_a) && is_just(m_b)) return C(internal::invoke(f, unsafe_get_just(m_a), unsafe_get_just(m_b))); return C(def); } // API search type: join_maybe : Maybe Maybe a -> Maybe a // Flattens a nested maybe. // join_maybe(Just Just x) == Just x // join_maybe(Just Nothing) == Nothing // join_maybe(Nothing) == Nothing template maybe join_maybe(const maybe>& m) { if (is_just(m)) return unsafe_get_just(m); else return nothing(); } // API search type: and_then_maybe : ((a -> Maybe b), (Maybe a)) -> Maybe b // fwd bind count: 1 // Monadic bind. // Returns nothing if the maybe already is nothing. // Otherwise return the result of applying // the function to the just value of the maybe. template auto and_then_maybe(F f, const maybe& m) { internal::trigger_static_asserts(); using FOut = std::decay_t>; static_assert(internal::is_maybe::value, "Function must return a maybe<> type"); if (is_just(m)) return internal::invoke(f, unsafe_get_just(m)); else return nothing(); } // API search type: compose_maybe : ((a -> Maybe b), (b -> Maybe c)) -> (a -> Maybe c) // Left-to-right Kleisli composition of monads. // Composes multiple callables taking a value and returning Maybe. // If the first callable returns a just, the value from the just // is extracted and shoved into the next callable. // If the first callable returns a nothing, it remains a nothing. // The first callable can take a variadic number of parameters. template auto compose_maybe(Callables&&... callables) { auto bind_maybe = [](auto f, auto g) { // next step would be to perfectly forward callables, as shown here: // https://vittorioromeo.info/index/blog/capturing_perfectly_forwarded_objects_in_lambdas.html return [f = std::move(f), g = std::move(g)](auto&&... args) { using FOut = std::decay_t< internal::invoke_result_t>; static_assert(internal::is_maybe::value, "Functions must return a maybe<> type"); using GOut = std::decay_t< internal::invoke_result_t>; static_assert(internal::is_maybe::value, "Functions must return a maybe<> type"); auto maybeB = internal::invoke(f, std::forward(args)...); if (is_just(maybeB)) return internal::invoke(g, unsafe_get_just(maybeB)); return GOut{}; }; }; return internal::compose_binary_lift(bind_maybe, std::forward(callables)...); } // API search type: flatten_maybe : (Maybe (Maybe a)) -> Maybe a // fwd bind count: 0 // Also known as join. template maybe flatten_maybe(const maybe>& maybe_maybe) { if (is_nothing(maybe_maybe)) return nothing(); return unsafe_get_just(maybe_maybe); } } // namespace fplus