From 75dd4210f2f2155aa965e11a0198b9b6b333399e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 21 Nov 2023 03:01:33 +0100 Subject: [PATCH] Library: RandomDraw - must accept generic arguments ...since the Policy class now defines the function signature, we can no longer assume that "input" is size_t. Rather, all invocations must rely on the generic adaptaion scheme. Getting this correct turns out rather tricky again; best to rely on a generic function-composition. Indeed I programmed such a helper several years ago, with the caveat that at that time we used C++03 and could not perfect-forward arguments. Today this problem can be solved much more succinct using generic Lambdas. --- src/lib/meta/function-closure.hpp | 148 ++++------------------------- src/lib/random-draw.hpp | 49 +++++----- src/lib/wrapper.hpp | 67 +++++-------- tests/library/random-draw-test.cpp | 20 +++- wiki/thinkPad.ichthyo.mm | 137 +++++++++++++++++++++++++- 5 files changed, 221 insertions(+), 200 deletions(-) diff --git a/src/lib/meta/function-closure.hpp b/src/lib/meta/function-closure.hpp index 6e435b752..74d5d18f0 100644 --- a/src/lib/meta/function-closure.hpp +++ b/src/lib/meta/function-closure.hpp @@ -644,123 +644,6 @@ namespace func{ - - - - - namespace _composed { // repetitive impl.code for function composition - using std::bind; - using std::function; - using std::placeholders::_1; - using std::placeholders::_2; - using std::placeholders::_3; - using std::placeholders::_4; - using std::placeholders::_5; - using std::placeholders::_6; - using std::placeholders::_7; - using std::placeholders::_8; - using std::placeholders::_9; - - template - struct Build; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2,_3)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2,_3,_4)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2,_3,_4,_5)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2,_3,_4,_5,_6)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2,_3,_4,_5,_6,_7)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2,_3,_4,_5,_6,_7,_8)); } - }; - - template - struct Build - { - static function func(F1& f1, F2& f2) { return bind (f2, bind (f1,_1,_2,_3,_4,_5,_6,_7,_8,_9)); } - }; - } // (End) impl namespace (_composed) - - - - /** - * Functional composition. Create a functor, which - * on invocation will execute two functions chained, - * i.e. fed the result of invoking the first function - * as argument into the second function. - */ - template - class FunctionComposition - { - typedef typename _Fun::Args Args; - typedef typename _Fun::Ret Ret1; - - typedef Types ArgsF2; - typedef typename FunctionTypedef::Sig SigF2; - typedef typename FunctionTypedef::Sig ChainedSig; - - enum { ARG_CNT = count::value }; - - - public: - static function - chain (F1& f1, SigF2& f2) - { - return _composed::Build::func (f1,f2); - } - static function - chain (F1& f1, function& f2) - { - return _composed::Build, ARG_CNT>::func (f1,f2); - } - }; - - - /** * Bind a specific argument to an arbitrary value. * Especially, this "value" might be another binder. @@ -826,13 +709,13 @@ namespace func{ typedef FunctionClosure Type; }; - template + template struct _Chain { - typedef typename _Fun::Args Args; - typedef typename _Fun::Ret Ret; - typedef typename FunctionTypedef::Sig Chained; - typedef function Function; + typedef typename _Fun::Args Args; + typedef typename _Fun::Ret Ret; + typedef typename FunctionTypedef::Sig ChainedSig; + typedef function Functor; }; template @@ -946,14 +829,21 @@ namespace func{ } - /** build a functor chaining the given functions */ - template - inline - typename _Chain::Function - chained (SIG1& f1, SIG2& f2) + /** build a functor chaining the given functions: feed the result of f1 into f2. + * @note the mathematical notation would be `chained ≔ f2 ∘f1` + */ + template + inline auto + chained (FUN1&& f1, FUN2&& f2) { - typedef typename _Chain::Ret Ret; - return FunctionComposition::chain (f1, f2); + using Ret = typename _Chain::Ret; + + return [functor1 = std::forward (f1) + ,functor2 = std::forward (f2)] + (auto&& ...args) -> Ret + { + return functor2 (functor1 (std::forward (args)...)); + }; } diff --git a/src/lib/random-draw.hpp b/src/lib/random-draw.hpp index 513f49a81..79c480587 100644 --- a/src/lib/random-draw.hpp +++ b/src/lib/random-draw.hpp @@ -52,6 +52,7 @@ #include "lib/meta/function.hpp" +#include "lib/meta/function-closure.hpp" #include "lib/util.hpp" #include @@ -138,10 +139,11 @@ namespace lib { if (probability_ == 0.0 or val == 0.0) return Tar{0}; double q = (1.0 - probability_); + auto org = Tar::minVal(); val -= q; // [0 .. [q .. 1[ val /= probability_; // [0 .. 1[ - val *= maxResult_; // [0 .. m[ - val += 1; // [1 .. m] + val *= maxResult_ - org; // [0 .. m[ + val += org+1; // [1 .. m] val += CAP_EPSILON; // round down yet absorb dust return Tar{val}; } @@ -156,6 +158,13 @@ namespace lib { return double(hash % QUANTISER) / QUANTISER; } + /** @internal core operation: draw and quantise into limited value */ + Tar + drawLimited (size_t hash) + { + return limited (asRand (hash)); + } + public: /** Drawing is _disabled_ by default, always yielding "zero" */ @@ -190,7 +199,7 @@ namespace lib { } RandomDraw&& - maxVal (uint m) + maxVal (Tar m) { maxResult_ = m; return move (*this); @@ -228,33 +237,25 @@ namespace lib { static_assert (lib::meta::_Fun(), "Need something function-like."); using Res = typename lib::meta::_Fun::Ret; + using lib::meta::func::chained; - if constexpr (std::is_same_v) + if constexpr (std::is_same_v)// ◁──────────────────────────┨ function produces result directly return std::forward(fun); else - if constexpr (std::is_same_v) - return [functor=std::forward(fun), this] - (size_t rawHash) - { - size_t hash = functor(rawHash); - double randomNum = asRand (hash); - return limited (randomNum); - }; + if constexpr (std::is_same_v)// ◁───────────────────────┨ function yields random source to draw value + return chained (std::forward(fun) + ,[this](size_t hash){ return drawLimited(hash); }); else - if constexpr (std::is_same_v) - return [functor=std::forward(fun), this] - (size_t rawHash) - { - double randomNum = functor(rawHash); - return limited (randomNum); - }; + if constexpr (std::is_same_v)// ◁───────────────────────┨ function yields random value to be quantised + return chained (std::forward(fun) + ,[this](double rand){ return limited(rand); }); else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v)// ◁────────────────────┨ function yields parametrised RandomDraw to invoke return [functor=std::forward(fun), this] - (size_t rawHash) - { - RandomDraw parametricDraw = functor(rawHash); - return parametricDraw (rawHash); + (auto&& ...inArgs) + { // invoke with copy + RandomDraw parametricDraw = functor(inArgs...); + return parametricDraw (forward (inArgs)...); }; else static_assert (not sizeof(Res), "unable to adapt / handle result type"); diff --git a/src/lib/wrapper.hpp b/src/lib/wrapper.hpp index 7ee0ef197..37cea1cee 100644 --- a/src/lib/wrapper.hpp +++ b/src/lib/wrapper.hpp @@ -396,18 +396,16 @@ namespace wrapper { /** - * Extension of ItemWrapper: a function remembering - * the result of the last invocation. Initially, the "value" - * is bottom (undefined, NIL), until the function is invoked - * for the first time. After that, the result of the last - * invocation can be accessed by `operator* ()` - * - * @note non-copyable. (removing this limitation would - * require a much more expensive implementation, - * by implementing operator() ourselves) + * Extension of ItemWrapper: a function remembering the result of the + * last invocation. Initially, the "value" is bottom (undefined, NIL), + * until the function is invoked for the first time. After that, the + * result of the last invocation can be accessed by `operator* ()` + * @note deliberately non-copyable, since we capture a reference + * to `this` in order to write to the embedded ItemWrapper. + * (to alleviate, we'd have to re-link after copying/moving) */ template - class FunctionResult + struct FunctionResult : public function , util::NonCopyable { @@ -416,43 +414,28 @@ namespace wrapper { ResWrapper lastResult_; - - Res - captureResult (Res res) - { - lastResult_ = res; - return res; - } - public: - /** default ctor yields an object - * locked to \em invalid state */ - FunctionResult () { } + /** by default locked to _invalid state_ */ + FunctionResult() = default; - /** Create result-remembering functor - * by binding the given function. Explanation: - * - `*this` is a _function_ - * - initially it is defined as invalid - * - then we build the function composition of - * the target function, and a function storing - * the result value into the ResWrapper member - * - define ourselves by assigning the resulting - * composite function + /** + * Create result-remembering functor by outfitting a _copy_ + * of the given function with an adaptor to _capture_ each + * produced result. + * @warning if function result is a value, it is copied. */ template - FunctionResult (FUN targetFunction) - { - using std::bind; - using std::placeholders::_1; - using lib::meta::func::chained; - // note: binding "this" mandates noncopyable - function doCaptureResult = bind (&FunctionResult::captureResult, this, _1 ); - function chainedWithResCapture = chained (targetFunction, doCaptureResult); - - function::operator= (chainedWithResCapture); // define the function (baseclass) - } - + FunctionResult (FUN&& targetFunction) + : function{lib::meta::func::chained + ( std::forward (targetFunction) + , [this](Res res) -> Res + { + lastResult_ = res; + return std::forward (res); + })} + { } + /** retrieve the last function result observed */ Res& operator*() const { return *lastResult_; } bool isValid () const { return lastResult_.isValid(); } diff --git a/tests/library/random-draw-test.cpp b/tests/library/random-draw-test.cpp index 1f90133f1..6d1abde88 100644 --- a/tests/library/random-draw-test.cpp +++ b/tests/library/random-draw-test.cpp @@ -30,6 +30,7 @@ #include "lib/test/run.hpp" //#include "lib/test/test-helper.hpp" #include "lib/random-draw.hpp" +#include "lib/time/timevalue.hpp" #include "lib/test/diagnostic-output.hpp"////////////////////TODO //#include "lib/util.hpp" @@ -42,6 +43,8 @@ namespace test{ // using util::isSameObject; // using std::rand; + using lib::time::FSecs; + using lib::time::TimeVar; // namespace error = lumiera::error; // using error::LUMIERA_ERROR_FATAL; @@ -154,8 +157,21 @@ SHOW_EXPR (int(draw(256))); void verify_policy() { -// auto d1 = RandomDraw>().probability(1.0); -//SHOW_EXPR (uint(d1())) + auto d1 = RandomDraw>().probability(1.0); + uint v1 = d1(); + CHECK (0 < v1 and v1 <=5); + + struct SpecialPolicy + : function(char,uint)> + { + static double defaultSrc (char b, uint off) { return fmod ((b-'A'+off)/double('Z'-'A'), 1.0); } + }; + + auto d2 = RandomDraw().probability(1.0); + CHECK (d2('A', 2) == 'D'); + CHECK (d2('M',10) == 'X'); + CHECK (d2('Y', 0) == 'Z'); + CHECK (d2('Y',15) == 'P'); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 91b965ed6..67c1ca642 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -96642,10 +96642,82 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + + + + + + + + + + + +

+ geht heutzutage relativ kompakt mit generischen Lambdas +

+ + +
+ + + + + +

+ ...ich hab nicht „lesbar“ gesagt... +

+ + +
+ +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + +

+ muß die Funktionsargumente kopieren +

+ + +
+
+ + + +
+
+
+ + +
@@ -103542,6 +103614,65 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +

+ [](auto&& ...args) -> RetType { return fun (forward<decltype(args)> (args) ...); } +

+ + +
+
+ + + + + + + + +

+ weil bei normaler Typinferenz ein decay stattfindet +

+ + +
+ + + + + +

+ „zum Beispiel...“ wenn die im Lamba verwendete Funktion eine Referenz zurückliefert, dann passiert ein decay auf den unterliegenden Objekttyp, d.h. man erzeugt eine schwebende Kopie.... +

+ + +
+
+ + + + + + +

+ hab mich grad geschnitten und 5 Stunden nach der Ursache gesucht +

+ + +
+ +
+
+