diff --git a/CMakeLists.txt b/CMakeLists.txt index b042b02..3589530 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ link_libraries( alepha ) # The subdirs to build add_subdirectory( Meta ) add_subdirectory( Atomic ) +add_subdirectory( Functional ) add_subdirectory( Proof ) add_subdirectory( Memory ) add_subdirectory( IOStreams ) diff --git a/Concepts.h b/Concepts.h index 9e021bd..33e97c3 100644 --- a/Concepts.h +++ b/Concepts.h @@ -214,6 +214,13 @@ namespace Alepha::Hydrogen ::detail:: Concepts_m template< typename T > concept UnaryFunction= Functional< T > and function_traits< T >::args_size == 1; + template< typename T, typename Signature > + concept Function= ConvertibleTo< T, std::function< Signature > >; + + template< typename T, typename ReturnType > + concept FunctionReturning= Functional< T > + and ConvertibleTo< typename function_traits< T >::return_type, ReturnType >; + template< typename T > concept StandardLayout= std::is_standard_layout_v< T >; diff --git a/Functional/CMakeLists.txt b/Functional/CMakeLists.txt new file mode 100644 index 0000000..8802363 --- /dev/null +++ b/Functional/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( composition.test ) diff --git a/Functional/composition.h b/Functional/composition.h new file mode 100644 index 0000000..530bbe9 --- /dev/null +++ b/Functional/composition.h @@ -0,0 +1,137 @@ +static_assert( __cplusplus >= 2023'02 ); + +#pragma once + +#include + +#include + +#include + +namespace Alepha::Hydrogen::Functional ::detail:: composition_m +{ + inline namespace exports + { + constexpr struct + { + template< typename T > + constexpr T + operator() ( T t ) const + { + return t; + } + } ident; + + /*! + * Functional Composition utilities. + * + * All utilities herein use value/copy semantics. This is both because + * it makes it easier to implement, and because functional programming typically + * eschews reference and move semantics. + */ + } + + template< typename Outer, typename Inner, typename ReturnType, typename TupleOfArguments > + struct Composed; + + template< typename Outer, typename Inner, typename ReturnType, typename ... Args > + struct Composed< Outer, Inner, ReturnType, std::tuple< Args... > > + { + using outer_type= Outer; + using inner_type= Inner; + + using return_type= ReturnType; + + Outer outer; + Inner inner; + + return_type + operator() ( Args ... args ) + { + return outer( inner( std::forward< Args >( args )... ) ); + } + }; + + template< typename Intermediate, typename ReturnType > + auto + compose( Concepts::Function< ReturnType ( Intermediate ) > auto outer, Concepts::FunctionReturning< Intermediate > auto inner ) + { + using inner_traits= function_traits< decltype( inner ) >; + using params= typename inner_traits::args_type; + + return Composed< decltype( outer ), decltype( inner ), ReturnType, params >{ std::move( outer ), std::move( inner ) }; + } + + namespace exports + { + inline auto + operator * ( decltype( ident ), decltype( ident ) ) + { + return ident; + } + + inline auto + operator * ( decltype( ident ), Concepts::Functional auto inner ) + { + using Intermediate= typename function_traits< decltype( inner ) >::return_type; + + return []( Intermediate intermediate ) { return intermediate; } * inner; + } + + /*! + * Function composition operator. + * + * This is a C++ notation equivalent to the `∘` operator in mathematics + * for functions. Just as `f( g( x ) )` is `( f ∘ g )( x )`, the C++ + * equivalent notation is `( f * g )( x )`. + */ + inline auto + operator * ( Concepts::UnaryFunction auto outer, Concepts::Functional auto inner ) + { + using Intermediate= typename function_traits< decltype( inner ) >::return_type; + using ReturnType= typename function_traits< decltype( inner ) >::return_type; + + return compose< Intermediate, ReturnType >( std::move( outer ), std::move( inner ) ); + } + + inline auto + operator >> ( decltype( ident ), decltype( ident ) ) + { + return ident; + } + + inline auto + operator >> ( Concepts::Functional auto first, decltype( ident ) ) + { + return ident * first; + } + + /*! + * Ordered function composition operator. + * + * While `(f * g * h)( x )` preserves the order in the + * notation `f( g( h( x ) ) )`, sometimes it's desirable to + * write the composition in "first, then" style syntax. This + * example would be `h`, then `g`, then `f`. For this purpose, + * `operator >>` has been overloaded bewteen two functions to + * permit simple examples such as: + * + * `( first >> second >> third )( arg )` + * + * This operator uses the same underpinnings as the `operator *`, + * and thus both syntaxes (and captures of their combinations) can + * be mixed and matched. + */ + + inline auto + operator >> ( Concepts::Functional auto first, Concepts::Functional auto then ) + { + return then * first; + } + } +} + +namespace Alepha::Hydrogen::Functional::inline exports::inline composition_m +{ + using namespace detail::composition_m::exports; +} diff --git a/Functional/composition.test/0.cc b/Functional/composition.test/0.cc new file mode 100644 index 0000000..5a9250f --- /dev/null +++ b/Functional/composition.test/0.cc @@ -0,0 +1,48 @@ +static_assert( __cplusplus >= 2023'02 ); + +#include + +#include + +#include + +namespace +{ + using namespace Alepha::Testing::exports; + + + auto init= Alepha::Utility::enroll <=[] + { + "smoke test"_test <=[]( Environment test ) + { + using namespace Alepha::Functional::exports::composition_m; + + auto i= ident * ident; + + auto add_two= []( const int x ) + { + return x + 2; + }; + + auto mult_two= []( const int x ) { return x * 2; }; + + auto add_two_then_mult_two= mult_two * add_two; + + test.expect( add_two_then_mult_two( 1 ) == 6 ); + }; + + "compose test"_test <=[]( Environment test ) + { + using namespace Alepha::Functional::exports::composition_m; + + auto add= []( const int x, const int y ) + { + return x + y; + }; + + auto mult3= []( const int x ) { return x * 3; }; + + test.expect( ( add >> mult3 )( 2, 1 ) == 9 ); + }; + }; +} diff --git a/Functional/composition.test/CMakeLists.txt b/Functional/composition.test/CMakeLists.txt new file mode 100644 index 0000000..b099603 --- /dev/null +++ b/Functional/composition.test/CMakeLists.txt @@ -0,0 +1 @@ +unit_test( 0 ) diff --git a/Testing/test.h b/Testing/test.h index 7124949..d069477 100644 --- a/Testing/test.h +++ b/Testing/test.h @@ -138,6 +138,7 @@ namespace Alepha::Hydrogen::Testing }; using TestState= TestStateCore &; + using Environment= TestState; } inline auto