From e127d0ad9a632cc4bda375e762862396030b56db Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 20 Nov 2023 02:00:56 +0100 Subject: [PATCH] Chain-Load: develop a design for a random-number-drawing DSL This might seem totally overblown -- but already the development of this prototype showed me time and again, that it is warranted. Because it is damn hard to get the probabilities and the mappings to fixed output values correct. After in-depth analysis, I decided completely to abandon the initially chosen approach with the Cap helper, where the user just specifies an upper and lower bound. While this seems compellingly simple at start, it directly lures into writing hard-to-understand code tied to the implementation logic. With the changed approach, most code should get along rather with auto myRule = Draw().probabilty(0.6).maxVal(4); ...which is obviously a thousand times more legible than any kind of tricky modulus expressions with shifted bounds. --- research/try.cpp | 197 ++++++++++++++++-- wiki/thinkPad.ichthyo.mm | 422 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 603 insertions(+), 16 deletions(-) diff --git a/research/try.cpp b/research/try.cpp index 85449245f..95b323296 100644 --- a/research/try.cpp +++ b/research/try.cpp @@ -57,6 +57,18 @@ * limited to a very small value range. While numerically simple, this turns out to be rather * error-prone, hence the desire to put a DSL in front. The challenge however arises from * the additional requirement to support various usage patters, all with minimal specs. + * + * The following code lays out the ground structure, while treating Spec as a distinct + * type, which is then mixed into Draw. This logical separation basically was led me to the + * final solution: Draw both _is_ a function and _embodies_ the implementation of this function. + * This somewhat surprising layout is what enables use as a DSL builder, because it allows both + * to have the _builder use_ and the _converter use_ in the same class, even allowing to _define_ + * a Draw by giving a function which _produces_ a (dynamically parametrised) Draw. + * + * In this prototype, all of the functor adaptation is also part of the Draw template; for the + * real implementation this will have to be supplied at usage site through a traits template, + * otherwise it would not be possible to integrate seamlessly with custom data sources (as + * happens in the intended use case, where actually a Node is the data source) */ typedef unsigned int uint; @@ -69,20 +81,24 @@ typedef unsigned int uint; #include "lib/meta/function.hpp" #include +#include +using lib::meta::_Fun; using std::function; +using std::forward; +using std::move; template struct Limited { - static constexpr T min() { return T(0); } - static constexpr T max() { return max; } + static constexpr T minVal() { return T(0); } + static constexpr T maxVal() { return max; } T val; template Limited (X raw) - : val(util::limited (X(min()), raw, X(max()))) + : val(util::limited (X(minVal()), raw, X(maxVal()))) { } }; @@ -90,22 +106,26 @@ template struct Spec { using Lim = Limited; - static constexpr double CAP_EPSILON = 0.001; + static constexpr double CAP_EPSILON = 0.0001; - double lower{0}; - double upper{max}; + double probability{0}; + T maxResult{Lim::maxVal()}; Spec() = default; + explicit + Spec (double p) : probability{p}{ } + Lim limited (double val) { - if (val==lower) + if (probability == 0.0 or val == 0.0) return Lim{0}; - val -= lower; - val /= upper-lower; - val *= max; - val += CAP_EPSILON; + double q = (1.0 - probability); + val -= q; + val /= probability; + val *= maxResult; + val += 1 + CAP_EPSILON; return Lim{val}; } @@ -122,13 +142,136 @@ struct Draw Draw() : Spc{} - , Fun{[](size_t){ return Spc{}.limited(0); }} + , Fun{[this](size_t hash){ return Spc::limited (asRand (hash)); }} { } template - Draw(FUN fun) - : Fun{fun} + Draw(FUN&& fun) + : Spc{1.0} + , Fun{adaptOut(adaptIn(std::forward (fun)))} { } + + + Draw&& + probability (double p) + { + Spc::probability = p; + return move (*this); + } + + Draw&& + maxVal (uint m) + { + Spc::maxResult = m; + return move (*this); + } + + template + Draw&& + mapping (FUN&& fun) + { + Fun(*this) = adaptOut(adaptIn(std::forward (fun))); + return move (*this); + } + + + + double + asRand (size_t hash) + { + return double(hash % 256)/256; + } + + /** + * @internal helper to expose the signature `size_t(size_t)` + * by wrapping a given lambda or functor. + */ + template + struct Adaptor + { + static_assert (not sizeof(SIG), "Unable to adapt given functor."); + }; + + template + struct Adaptor + { + template + static decltype(auto) + build (FUN&& fun) + { + return std::forward(fun); + } + }; + + template + struct Adaptor + { + template + static auto + build (FUN&& fun) + { + return [functor=std::forward(fun)] + (size_t) + { + return functor(); + }; + } + }; + + + template + decltype(auto) + adaptIn (FUN&& fun) + { + static_assert (lib::meta::_Fun(), "Need something function-like."); + static_assert (lib::meta::_Fun::ARITY <= 1, "Function with zero or one argument expected."); + + using Sig = typename lib::meta::_Fun::Sig; + + return Adaptor::build (forward (fun)); + } + + template + decltype(auto) + adaptOut (FUN&& fun) + { + static_assert (lib::meta::_Fun(), "Need something function-like."); + static_assert (lib::meta::_Fun::ARITY ==1, "Function with exactly one argument required."); + + using Res = typename lib::meta::_Fun::Ret; + + if constexpr (std::is_same_v) + 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 Spc::limited (randomNum); + }; + else + if constexpr (std::is_same_v) + return [functor=std::forward(fun), this] + (size_t rawHash) + { + double randomNum = functor(rawHash); + return Spc::limited (randomNum); + }; + else + if constexpr (std::is_same_v) + return [functor=std::forward(fun), this] + (size_t rawHash) + { + Draw parametricDraw = functor(rawHash); + return parametricDraw (rawHash); + }; + else + static_assert (not sizeof(Res), "unable to adapt / handle result type"); + NOTREACHED("Handle based on return type"); + } + }; int @@ -140,10 +283,34 @@ main (int, char**) D draw; SHOW_EXPR(draw) SHOW_EXPR(draw(5).val) - draw = D{[](size_t i){ return S{}.limited(i); }}; + draw = D{[](size_t i){ return 0.75; }}; SHOW_EXPR(draw(5).val) + draw = D{}.probability(0.25).maxVal(5); +SHOW_EXPR(draw(256-65).val) +SHOW_EXPR(draw(256-64).val) +SHOW_EXPR(draw(256-64+ 1).val) +SHOW_EXPR(draw(256-64+10).val) +SHOW_EXPR(draw(256-64+11).val) +SHOW_EXPR(draw(256-64+12).val) +SHOW_EXPR(draw(256-64+13).val) +SHOW_EXPR(draw(256-64+23).val) +SHOW_EXPR(draw(256-64+24).val) +SHOW_EXPR(draw(256-64+25).val) +SHOW_EXPR(draw(256-64+26).val) +SHOW_EXPR(draw(256-64+36).val) +SHOW_EXPR(draw(256-64+37).val) +SHOW_EXPR(draw(256-64+38).val) +SHOW_EXPR(draw(256-64+39).val) +SHOW_EXPR(draw(256-64+49).val) +SHOW_EXPR(draw(256-64+50).val) +SHOW_EXPR(draw(256-64+51).val) +SHOW_EXPR(draw(256-64+52).val) +SHOW_EXPR(draw(256-64+62).val) +SHOW_EXPR(draw(256-64+63).val) +SHOW_EXPR(draw(256).val) + cout << "\n.gulp.\n"; return 0; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ba38e5350..ed2d4164c 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -96122,13 +96122,433 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ will sagen: es ist nicht hilftreich, +

+

+ das kombinieren zu wollen... +

+ + +
+ + + + + +

+ ....aber irgendwie scheint mir genau das vorzuschweben... +

+ + +
+
+ + + + + + +

+ ...zumindest der Möglichkeit nach +

+ + +
+
+ + + + + + +

+ hab das Gefühl daß ich schon wieder mal viel zu weit ausgreife, für ein Stück Infrastruktur, das einem einzigen Zweck dient (noch dazu lediglich für Tests) und dann wahrscheinlich nur noch rumliegt und zufällig vorbeikommende Leser verwirrt +

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

+ mir sind die im Moment ganz klar, dagegen beim Leser kann man nur so etwas wie eine generelle Idee von einem DAG voraussetzen +

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

+ aber Kaskadieren der Auswertung bleibt eine Möglichkeit +

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

+ konsequenterweise sollte die ganze Freiheit +

+

+ und Offenheit für beliebige Rechenregeln +

+

+ nun komplett aufgegeben werden +

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

+ und gemäß YAGNI werde ich nur 10% der Möglichkeiten  wirklich brauchen +

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

+ ...der müßte dann aber absolut gelten, und Teil der Spec werden. Ja warum nicht +

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

+ ...da sich alle auf den gleichen Hash beziehen, und daher auch korreliert ziehen werden +

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

+ ...diese Variante ist erst mit C++17 möglich, da nun der Template-Parameter inferiert werden kann +

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