* 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.
435 lines
16 KiB
C++
435 lines
16 KiB
C++
/*
|
||
FunctionComposition(Test) - functional composition and partial application
|
||
|
||
Copyright (C)
|
||
2009, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**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.
|
||
|
||
* *****************************************************************/
|
||
|
||
/** @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>
|
||
|
||
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
|
||
* - 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();
|
||
}
|
||
|
||
|
||
/** 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
|
||
typedef function<Sig23> F23; // 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 res = 0;
|
||
res = fun_23 (_2_,_3_).o_; // and invoke the resulting functor ("closure"), providing the remaining arguments
|
||
CHECK (23 == res);
|
||
|
||
|
||
|
||
// Version2: extract the binding arguments from a tuple--- //
|
||
|
||
using PartialArg = Tuple<Types<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)
|
||
);
|
||
res = 0;
|
||
res = fun_23 (_2_,_3_).o_; // and invoke the resulting functor....
|
||
CHECK (23 == res);
|
||
|
||
|
||
|
||
|
||
// Version3: let the PApply-template do the work for us--- //
|
||
|
||
typedef Types<Num<1>> ArgTypes; // 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....
|
||
res = 0;
|
||
res = fun_23 (_2_,_3_).o_; // invoke the resulting functor...
|
||
CHECK (23 == res);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// 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
|
||
|
||
res = 0;
|
||
res = fun_23 (_2_,_3_).o_; // invoke the resulting functor...
|
||
CHECK (23 == res);
|
||
|
||
|
||
|
||
// 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));
|
||
res = fun_23 (_2_,_3_).o_;
|
||
CHECK (24 == res);
|
||
|
||
typedef function<Num<1>(Num<1>, Num<2>)> F12;
|
||
F12 fun_12 = func::applyLast(f, Num<3>(20)); // close the *last* argument of a function
|
||
res = fun_12 (_1_,_2_).o_;
|
||
CHECK (23 == res);
|
||
|
||
fun_12 = func::applyLast(func123, Num<3>(21)); // alternatively use a function object
|
||
res = fun_12 (_1_,_2_).o_;
|
||
CHECK (24 == res);
|
||
|
||
Sig123 *fP = &f; // a function pointer works too
|
||
fun_12 = func::applyLast( fP, Num<3>(22));
|
||
res = fun_12 (_1_,_2_).o_;
|
||
CHECK (25 == res);
|
||
// 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_);
|
||
|
||
|
||
|
||
// 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
|
||
typedef Types<Num<3>,Num<2>,Num<1>> Args2Close; // Tuple type to hold the 3 argument values used for the closure
|
||
|
||
// 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_)
|
||
);
|
||
|
||
// apply the remaining argument values
|
||
Num<5> resN5 = fun_54 (_5_,_4_);
|
||
CHECK (5+4+3+2+1 == resN5.o_);
|
||
}
|
||
|
||
|
||
|
||
|
||
void
|
||
check_functionalComposition ()
|
||
{
|
||
typedef int Sig2(Num<1>);
|
||
typedef Num<1> Sig11(Num<1>);
|
||
typedef Num<1> Sig12(Num<1>,Num<2>);
|
||
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_) );
|
||
|
||
|
||
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>;
|
||
Tuple<Types<char>> argT(55);
|
||
|
||
function<SigR1> f_bound_1 = BindToArgument<Sig15,char,0>::reduced (f, argT);
|
||
function<SigR2> f_bound_2 = BindToArgument<Sig15,char,1>::reduced (f, argT);
|
||
function<SigR3> f_bound_3 = BindToArgument<Sig15,char,2>::reduced (f, argT);
|
||
function<SigR4> f_bound_4 = BindToArgument<Sig15,char,3>::reduced (f, argT);
|
||
function<SigR5> f_bound_5 = BindToArgument<Sig15,char,4>::reduced (f, argT);
|
||
|
||
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, argT);
|
||
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");
|
||
|
||
|
||
|
||
}}} // namespace lib::meta::test
|