/* FunctionComposition(Test) - functional composition and partial application Copyright (C) 2009, Hermann Vosseler   **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 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 Num fun11 ( Num val1 ) { return val1; } template Num fun12 ( Num val1 , Num val2 ) { val1.o_ += val2.o_; return val1; } template Num fun13 ( Num val1 , Num val2 , Num val3 ) { val1.o_ += val2.o_ + val3.o_; return val1; } template Num fun14 ( Num val1 , Num val2 , Num val3 , Num val4 ) { val1.o_ += val2.o_ + val3.o_ + val4.o_; return val1; } template Num fun15 ( Num val1 , Num val2 , Num val3 , Num val4 , Num val5 ) { val1.o_ += val2.o_ + val3.o_ + val4.o_ + val5.o_; return val1; } /** "Function-2" can be chained behind fun1 */ template 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 using F23 = function; // 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, 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>; // now package just the argument(s) to be applied into a tuple Tuple args_to_bind{Num<1>(18)}; fun_23 = PApply::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 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<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_); // 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<2>, Num<1>>; // Tuple type to hold the 3 argument values used for the closure // Close the trailing 3 arguments of the 5-argument function... function fun_54 = PApply::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 f5_fun = f5; // also works with function objects... function 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 f_bound_1 = BindToArgument::reduced (f, 55); function f_bound_2 = BindToArgument::reduced (f, 55); function f_bound_3 = BindToArgument::reduced (f, 55); function f_bound_4 = BindToArgument::reduced (f, 55); function f_bound_5 = BindToArgument::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 f_bound_X = BindToArgument::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 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::Sig; using SigC = _Fun::Sig; using SigP = _Fun::Sig; CHECK (showType() == "double (float&, int&, long)"_expect); CHECK (showType() == "long (float&, int&, long)"_expect); CHECK (showType() == "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