lumiera_/tests/library/meta/function-composition-test.cpp

439 lines
17 KiB
C++
Raw Normal View History

/*
FunctionComposition(Test) - functional composition and partial application
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright (C)
2009, Hermann Vosseler <Ichthyostega@web.de>
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* *****************************************************************/
/** @file function-composition-test.cpp
** unit test \ref FunctionComposition_test
*/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/meta/typelist.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/function-closure.hpp"
#include "meta/typelist-diagnostics.hpp"
#include <tuple>
2011-12-03 02:56:50 +01:00
namespace lib {
namespace meta {
namespace test {
using ::test::Test;
// using lib::test::showType;
using lib::meta::_Fun;
using func::applyFirst;
using func::applyLast;
using func::bindLast;
using func::PApply;
using func::BindToArgument;
using std::make_tuple;
using std::get;
namespace { // test functions
Num<1> _1_;
Num<2> _2_;
Num<3> _3_;
Num<4> _4_;
Num<5> _5_;
Num<6> _6_;
Num<7> _7_;
Num<8> _8_;
Num<9> _9_;
/** "Function-1" will be used at the front side, accepting a tuple of values */
template<uint i>
Num<i>
fun11 ( Num<i> val1
)
{
return val1;
}
template<uint i, uint ii>
Num<i>
fun12 ( Num<i> val1
, Num<ii> val2
)
{
val1.o_ += val2.o_;
return val1;
}
template<uint i, uint ii, uint iii>
Num<i>
fun13 ( Num<i> val1
, Num<ii> val2
, Num<iii> val3
)
{
val1.o_ += val2.o_ + val3.o_;
return val1;
}
template<uint i, uint ii, uint iii, uint iv>
Num<i>
fun14 ( Num<i> val1
, Num<ii> val2
, Num<iii> val3
, Num<iv> val4
)
{
val1.o_ += val2.o_ + val3.o_ + val4.o_;
return val1;
}
template<uint i, uint ii, uint iii, uint iv, uint v>
Num<i>
fun15 ( Num<i> val1
, Num<ii> val2
, Num<iii> val3
, Num<iv> val4
, Num<v> val5
)
{
val1.o_ += val2.o_ + val3.o_ + val4.o_ + val5.o_;
return val1;
}
/** "Function-2" can be chained behind fun1 */
template<class II>
int
fun2 (II val)
{
return val.o_;
}
} // (End) test data
/**************************************************************************//**
* @test this test covers some extensions and variations on function closures:
* - partial application of a function, returning a partial closure
2012-01-07 00:02:03 +01:00
* - variation: binding an arbitrary term, might even be a nested binder
* - chaining of two functions with suitable arguments ("composition")
*/
class FunctionComposition_test : public Test
{
virtual void
run (Arg)
{
check_diagnostics();
check_partialApplication();
check_functionalComposition();
check_bindToArbitraryParameter();
verify_referenceHandling();
}
2012-01-07 00:02:03 +01:00
/** verify the test input data */
void
check_diagnostics ()
{
CHECK (6 == (fun13<1,2,3> (_1_, _2_, _3_)).o_ );
CHECK (6 == (fun13<1,1,1> (Num<1>(3), Num<1>(2), Num<1>(1))).o_ );
CHECK ( 1 == fun2 (fun11<1> (_1_)) );
CHECK ( 3 == fun2 (fun12<1,2> (_1_, _2_)) );
CHECK ( 6 == fun2 (fun13<1,2,3> (_1_, _2_, _3_)) );
CHECK (10 == fun2 (fun14<1,2,3,4> (_1_, _2_, _3_, _4_)) );
CHECK (15 == fun2 (fun15<1,2,3,4,5> (_1_, _2_, _3_, _4_, _5_)) );
CHECK ( 9 == fun2 (fun13<2,3,4> (_2_, _3_, _4_)) );
CHECK (18 == fun2 (fun13<5,6,7> (_5_, _6_, _7_)) );
CHECK (24 == fun2 (fun13<9,8,7> (_9_, _8_, _7_)) );
}
void
check_partialApplication ()
{
// Because the code of the partial function application is very technical,
// the following might serve as explanation what actually happens....
// (and actually it's a leftover from initial debugging)
typedef Num<1> Sig123(Num<1>, Num<2>, Num<3>); // signature of the original function
typedef Num<1> Sig23(Num<2>, Num<3>); // signature after having closed over the first argument
using F23 = function<Sig23>; // and a std::function object to hold such a function
Sig123& f = fun13<1,2,3>; // the actual input: a reference to the bare function
// Version1: do a direct argument binding----------------- //
using PH1 = std::_Placeholder<1>; // std::function argument placeholders
using PH2 = std::_Placeholder<2>;
PH1 ph1; // these empty structs are used to mark the arguments to be kept "open"
PH2 ph2;
Num<1> num18 (18); // ...and this value is for closing the first function argument
F23 fun_23 = std::bind (f, num18 // do the actual binding (i.e. close the first argument with a constant value)
, ph1
, ph2
);
int r1 = fun_23 (_2_,_3_).o_; // and invoke the resulting functor ("closure"), providing the remaining arguments
CHECK (23 == r1); // result ≡ num18 + _2_ + _3_ ≙ 18 + 2 + 3
// Version2: extract the binding arguments from a tuple--- //
using PartialArg = Tuple<TySeq<Num<1>, PH1, PH2>>; // Tuple type to hold the binding values. Note the placeholder types
PartialArg arg{num18, PH1(), PH2()}; // Value for partial application (the placeholders are default constructed)
fun_23 = std::bind (f, get<0>(arg) // now extract the values to bind from this tuple
, get<1>(arg)
, get<2>(arg)
);
int r2 = fun_23 (_2_,_3_).o_; // and invoke the resulting functor....
CHECK (23 == r2);
// function-closure.hpp defines a shorthand for this operation
fun_23 = func::bindArgTuple (f, arg);
int r3 = fun_23 (_2_,_3_).o_;
CHECK (23 == r3);
// Version3: let the PApply-template do the work for us--- //
using ArgTypes = TySeq<Num<1>>; // now package just the argument(s) to be applied into a tuple
Tuple<ArgTypes> args_to_bind{Num<1>(18)};
fun_23 = PApply<Sig123, ArgTypes>::bindFront (f , args_to_bind);
// "bindFront" will close the parameters starting from left....
int r4 = fun_23 (_2_,_3_).o_; // invoke the resulting functor...
CHECK (23 == r4);
// Version4: as you'd typically do it in real life-------- //
/*
fun_23 = func::applyFirst (f, Num<1>(18)); // use the convenience function API to close over a single value
int r5 = fun_23(_2_,_3_).o_; // invoke the resulting functor...
CHECK (23 == r5);
// what follows is the real unit test...
function<Sig123> func123{f}; // alternatively do it with an std::function object
fun_23 = func::applyFirst (func123, Num<1>(19));
int r5 = fun_23(_2_,_3_).o_;
CHECK (24 == r5);
using F12 = function<Num<1>(Num<1>, Num<2>)>;
F12 fun_12 = func::applyLast (f, Num<3>(20)); // close the *last* argument of a function
int r6 = fun_12(_1_,_2_).o_;
CHECK (23 == r6);
fun_12 = func::applyLast (func123, Num<3>(21)); // alternatively use a function object
int r7 = fun_12(_1_,_2_).o_;
CHECK (24 == r7);
Sig123* fP = &f; // a function pointer works too
fun_12 = func::applyLast (fP, Num<3>(22));
int r8 = fun_12(_1_,_2_).o_;
CHECK (25 == r8);
// cover more cases....
CHECK (1 == (func::applyLast (fun11<1> , _1_ ) ( ) ).o_);
CHECK (1+3 == (func::applyLast (fun12<1,3> , _3_ ) (_1_) ).o_);
CHECK (1+3+5 == (func::applyLast (fun13<1,3,5> , _5_ ) (_1_,_3_) ).o_);
CHECK (1+3+5+7 == (func::applyLast (fun14<1,3,5,7> , _7_ ) (_1_,_3_,_5_) ).o_);
CHECK (1+3+5+7+9 == (func::applyLast (fun15<1,3,5,7,9>, _9_ ) (_1_,_3_,_5_,_7_)).o_);
CHECK (9+8+7+6+5 == (func::applyFirst(fun15<9,8,7,6,5>, _9_ ) (_8_,_7_,_6_,_5_)).o_);
CHECK ( 8+7+6+5 == (func::applyFirst( fun14<8,7,6,5>, _8_ ) (_7_,_6_,_5_)).o_);
CHECK ( 7+6+5 == (func::applyFirst( fun13<7,6,5>, _7_ ) (_6_,_5_)).o_);
CHECK ( 6+5 == (func::applyFirst( fun12<6,5>, _6_ ) (_5_)).o_);
CHECK ( 5 == (func::applyFirst( fun11<5>, _5_ ) ( )).o_);
*/
2009-07-01 04:47:52 +02:00
// Finally a more convoluted example
// covering the general case of partial function closure:
typedef Num<5> Sig54321 (Num<5>, Num<4>, Num<3>, Num<2>, Num<1>); // Signature of the 5-argument function
typedef Num<5> Sig54 (Num<5>, Num<4>); // ...closing the last 3 arguments should yield this 2-argument function
using Args2Close = TySeq<Num<3>, Num<2>, Num<1>>; // Tuple type to hold the 3 argument values used for the closure
2009-07-01 04:47:52 +02:00
// Close the trailing 3 arguments of the 5-argument function...
function<Sig54> fun_54 = PApply<Sig54321,Args2Close>::bindBack (fun15<5,4,3,2,1>
,make_tuple (_3_,_2_,_1_)
2009-07-01 04:47:52 +02:00
);
// apply the remaining argument values
Num<5> resN5 = fun_54(_5_,_4_);
CHECK (5+4+3+2+1 == resN5.o_);
}
void
check_functionalComposition ()
{
2009-07-03 13:49:12 +02:00
typedef int Sig2(Num<1>);
typedef Num<1> Sig11(Num<1>);
2009-07-01 04:47:52 +02:00
typedef Num<1> Sig12(Num<1>,Num<2>);
2009-07-03 13:49:12 +02:00
typedef Num<1> Sig13(Num<1>,Num<2>,Num<3>);
typedef Num<1> Sig14(Num<1>,Num<2>,Num<3>,Num<4>);
typedef Num<1> Sig15(Num<1>,Num<2>,Num<3>,Num<4>,Num<5>);
Sig2 & ff = fun2< Num<1> >;
Sig11& f1 = fun11<1>;
Sig12& f2 = fun12<1,2>;
Sig13& f3 = fun13<1,2,3>;
Sig14& f4 = fun14<1,2,3,4>;
Sig15& f5 = fun15<1,2,3,4,5>;
CHECK (1 == func::chained(f1, ff) (_1_) );
CHECK (1+2 == func::chained(f2, ff) (_1_,_2_) );
CHECK (1+2+3 == func::chained(f3, ff) (_1_,_2_,_3_) );
CHECK (1+2+3+4 == func::chained(f4, ff) (_1_,_2_,_3_,_4_) );
CHECK (1+2+3+4+5 == func::chained(f5, ff) (_1_,_2_,_3_,_4_,_5_) );
2009-07-09 17:22:44 +02:00
function<Sig15> f5_fun = f5; // also works with function objects...
function<Sig2> ff_fun = ff;
CHECK (1+2+3+4+5 == func::chained(f5_fun, ff ) (_1_,_2_,_3_,_4_,_5_) );
CHECK (1+2+3+4+5 == func::chained(f5, ff_fun) (_1_,_2_,_3_,_4_,_5_) );
CHECK (1+2+3+4+5 == func::chained(f5_fun, ff_fun) (_1_,_2_,_3_,_4_,_5_) );
}
void
check_bindToArbitraryParameter ()
{
typedef Num<1> Sig15(Num<1>,Num<2>,Num<3>,Num<4>,Num<5>);
typedef Num<1> SigR1( Num<2>,Num<3>,Num<4>,Num<5>);
typedef Num<1> SigR2(Num<1>, Num<3>,Num<4>,Num<5>);
typedef Num<1> SigR3(Num<1>,Num<2>, Num<4>,Num<5>);
typedef Num<1> SigR4(Num<1>,Num<2>,Num<3>, Num<5>);
typedef Num<1> SigR5(Num<1>,Num<2>,Num<3>,Num<4> );
typedef Num<5> SigA5(Num<5>);
Sig15& f = fun15<1,2,3,4,5>;
SigA5& f5 = fun11<5>;
function<SigR1> f_bound_1 = BindToArgument<Sig15,char,0>::reduced (f, 55);
function<SigR2> f_bound_2 = BindToArgument<Sig15,char,1>::reduced (f, 55);
function<SigR3> f_bound_3 = BindToArgument<Sig15,char,2>::reduced (f, 55);
function<SigR4> f_bound_4 = BindToArgument<Sig15,char,3>::reduced (f, 55);
function<SigR5> f_bound_5 = BindToArgument<Sig15,char,4>::reduced (f, 55);
CHECK (55+2+3+4+5 == f_bound_1 ( _2_,_3_,_4_,_5_) );
CHECK (1+55+3+4+5 == f_bound_2 (_1_, _3_,_4_,_5_) );
CHECK (1+2+55+4+5 == f_bound_3 (_1_,_2_, _4_,_5_) );
CHECK (1+2+3+55+5 == f_bound_4 (_1_,_2_,_3_, _5_) );
CHECK (1+2+3+4+55 == f_bound_5 (_1_,_2_,_3_,_4_ ) );
// degenerate case: specify wrong argument position (behind end of argument list)
// causes the argument to be simply ignored and no binding to happen
function<Sig15> f_bound_X = BindToArgument<Sig15,char,5>::reduced (f, 88);
CHECK (1+2+3+4+5 == f_bound_X (_1_,_2_,_3_,_4_,_5_) );
/* check the convenient function-style API */
using std::bind;
f_bound_5 = bindLast (f, bind(f5, Num<5>(99)));
CHECK (1+2+3+4+99 == f_bound_5 (_1_,_2_,_3_,_4_ ) );
f_bound_5 = bindLast (f, bind(&f5, Num<5>(99))); // can bind function pointer
CHECK (1+2+3+4+99 == f_bound_5 (_1_,_2_,_3_,_4_ ) );
function<Sig15> asFunctor(f);
f_bound_5 = bindLast (asFunctor, bind(f5, Num<5>(88))); // use functor instead of direct ref
CHECK (1+2+3+4+88 == f_bound_5 (_1_,_2_,_3_,_4_ ) );
}
/** @internal static function to pass as reference for test */
static long floorIt (float it) { return long(floor (it)); }
/** @test ensure reference types and arguments are handled properly */
void
verify_referenceHandling()
{
int ii = 99;
float ff = 88;
auto fun = std::function{[](float& f, int& i, long l) -> double { return f + i + l; }};
auto& f1 = fun;
// build chained and a partially applied functors
auto chain = func::chained(f1,floorIt);
auto pappl = func::applyFirst (f1, ff);
using Sig1 = _Fun<decltype(f1)>::Sig;
using SigC = _Fun<decltype(chain)>::Sig;
using SigP = _Fun<decltype(pappl)>::Sig;
// CHECK (showType<Sig1>() == "double (float&, int&, long)"_expect);
// CHECK (showType<SigC>() == "long (float&, int&, long)"_expect);
// CHECK (showType<SigP>() == "double (int&, long)"_expect);
CHECK (220 == f1 (ff,ii,33));
CHECK (220 == chain(ff,ii,33));
CHECK (220 == pappl( ii,33));
// change original values to prove that references were
// passed and stored properly in the adapted functors
ii = 22;
ff = 42;
CHECK ( 97 == f1 (ff,ii,33));
CHECK ( 97 == chain(ff,ii,33));
CHECK ( 97 == pappl( ii,33));
// can even exchange the actual function, since f1 was passed as reference
fun = [](float& f, int& i, size_t s) -> double { return f - i - s; };
CHECK (-13 == f1 (ff,ii,33));
CHECK (-13 == chain(ff,ii,33));
CHECK (-13 == pappl( ii,33));
}
};
/** Register this test class... */
LAUNCHER (FunctionComposition_test, "unit common");
2011-12-03 02:56:50 +01:00
}}} // namespace lib::meta::test