From 3611bc94eec042a507810e6044f499636998b569 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Feb 2025 23:47:21 +0100 Subject: [PATCH] Invocation: investigate ways to provide partial application ...on top of the parameter-decorating functionality developed thus far. The idea is to allow in the `NodeBuilder` to supply ''some parameters'' directly, while the remaining parameters will be drawn from automation. Several years ago, I developed some helpers for partial function closure. Unfortunately these utils are somewhat limited, and rely on some pre-C++11 constructs, yet seem to be usable for the task at hand, since parameters are always expected as value objects by definition. This changeset shows a working proof-of concept for left-closing a parameter tuple with 5 elements; this turns out to surprisingly difficult due to the full genericity of the acceptable parameter-aggregates... --- src/lib/meta/function-closure.hpp | 8 +- src/lib/meta/variadic-rebind.hpp | 4 +- src/lib/random-draw.hpp | 1 - src/steam/engine/feed-manifold.hpp | 12 +- src/steam/engine/node-builder.hpp | 47 +++ src/steam/engine/weaving-pattern-builder.hpp | 2 +- tests/core/steam/engine/node-builder-test.cpp | 32 ++ wiki/thinkPad.ichthyo.mm | 373 +++++++++++++++++- 8 files changed, 464 insertions(+), 15 deletions(-) diff --git a/src/lib/meta/function-closure.hpp b/src/lib/meta/function-closure.hpp index 7c050cd18..d551e4b2e 100644 --- a/src/lib/meta/function-closure.hpp +++ b/src/lib/meta/function-closure.hpp @@ -33,6 +33,12 @@ ** Providing a one-shot function-style interface for this kind of manipulations ** is still considered beneficial, and thus we should gradually modernise ** the tools we want to retain... + ** @todo 2/25 note that there is a bind_front in C++20 and C++23 will provide a bind_back + ** helper, which would provide the implementation fully in accordance with current + ** expectations (including move, constexpr); if we choose to retain a generic + ** function-style front-end, it should be aligned with these standard facilities. + ** We might want to retain a simple generic interface especially for binding some + ** selected argument, which handles the intricacies of storing the functor. ** ** @see control::CommandDef usage example ** @see function.hpp @@ -412,7 +418,7 @@ namespace func{ * @tparam TAR full target tuple type. Some oft the elements within this tuple will * be default constructed, some will be initialised from the SRC tuple * @tparam SRC argument tuple type, for the values _actually to be initialised_ here. - * @tparam start position within TYPES, at which the sequence of init-arguments starts; + * @tparam start position within \a SRC, at which the sequence of init-arguments starts; * all other positions will just be default initialised * @see lib::meta::TupleConstructor */ diff --git a/src/lib/meta/variadic-rebind.hpp b/src/lib/meta/variadic-rebind.hpp index 1042fa128..3acc63783 100644 --- a/src/lib/meta/variadic-rebind.hpp +++ b/src/lib/meta/variadic-rebind.hpp @@ -141,11 +141,11 @@ namespace meta { using Penult = typename _Vari::Penult; using Ultima = typename _Vari::Ultima; - using _Tail_Pre_ = typename _Vari::Prefix; + using _Tail_Pre_ = typename _Vari::Prefix; using _Tail_Rev_ = typename _Vari::Revers; using Remain = L; - using Prefix = typename _Vari::Prepend; + using Prefix = typename _Vari::Prepend; using Revers = typename _Vari::Prepend; }; diff --git a/src/lib/random-draw.hpp b/src/lib/random-draw.hpp index 4de2cc419..14ecb9576 100644 --- a/src/lib/random-draw.hpp +++ b/src/lib/random-draw.hpp @@ -360,7 +360,6 @@ namespace lib { auto adaptIn (FUN&& fun) { - using lib::meta::func::applyFirst; using _Fun = lib::meta::_Fun; static_assert (_Fun(), "Need something function-like."); diff --git a/src/steam/engine/feed-manifold.hpp b/src/steam/engine/feed-manifold.hpp index 352495953..c1e099507 100644 --- a/src/steam/engine/feed-manifold.hpp +++ b/src/steam/engine/feed-manifold.hpp @@ -102,6 +102,7 @@ namespace engine { using lib::meta::ElmTypes; using lib::meta::NullType; using lib::meta::Tagged; + using lib::meta::TySeq; using std::is_pointer; using std::is_reference; using std::is_convertible; @@ -110,6 +111,7 @@ namespace engine { using std::remove_pointer_t; using std::tuple_element_t; using std::add_pointer_t; + using std::conditional_t; using std::__and_; using std::__not_; @@ -248,7 +250,7 @@ namespace engine { static constexpr bool hasParam() { return _Proc::hasParam(); } - using Param = std::conditional_t>; + using Param = conditional_t>; template using Res = typename _Fun::Ret; @@ -332,8 +334,8 @@ namespace engine { using BuffI = BuffS; using BuffO = BuffS; - using Param = std::conditional_t>; - using ArgI = std::conditional_t>; + using Param = conditional_t>; + using ArgI = conditional_t>; using ArgO = typename _Trait::SigO; @@ -373,7 +375,7 @@ namespace engine { using NotProvided = Tagged; template - using Provide_if = std::conditional_t>; + using Provide_if = conditional_t>; using FeedOutput = BufferSlot_Output; using FeedInput = Provide_if; @@ -561,6 +563,8 @@ namespace engine { }; using ElmsI = typename _Proc::ElmsI; using ElmsO = typename _Proc::ElmsO; + using ElmsP = conditional_t<_Trait::hasParam(), typename _Proc::ArgP, TySeq<>>; + using Param = typename _Proc::SigP; ///////////////////////////////////////////////////////////////////OOO qualify? template class META> using OutTypesApply = typename ElmsO::template Apply; diff --git a/src/steam/engine/node-builder.hpp b/src/steam/engine/node-builder.hpp index 4dc07103a..f4a2501a6 100644 --- a/src/steam/engine/node-builder.hpp +++ b/src/steam/engine/node-builder.hpp @@ -493,6 +493,53 @@ namespace engine { }; } + template + struct ClosureBuilder; + + template class TUP, typename...PARS> + struct ClosureBuilder> + { + using Params = TUP; + + static Params + buildParam (PARS ...params) + { + return {params...}; + } + + template + static auto + closeFront (VALS ...vs) + { + using lib::meta::_Fun; + using lib::meta::TySeq; + using lib::meta::func::PApply; + using ClosedTypes = TySeq; + using ParamBuilderSig = Params(PARS...); + auto partialClosure = PApply::bindFront (buildParam, std::make_tuple(vs...)); + using RemainingArgs = typename _Fun::Args; + using RemStripped = typename lib::meta::StripNullType::Seq; + using RemainingParams = typename lib::meta::RebindVariadic::Type; +// using idx = std::make_index_sequence; +// auto& makeParamAggregate = buildParam; + return [closure = move(partialClosure) + ] + (RemainingParams remPar) + { + return std::apply (closure, remPar); + }; + } + }; + + template + auto + closeParamFront (PAR v1, PARS ...vs) + { + using Params = typename WAB::Param; + return adaptParam( + ClosureBuilder::template closeFront (v1,vs...)); + } + /*************************************************************//** diff --git a/src/steam/engine/weaving-pattern-builder.hpp b/src/steam/engine/weaving-pattern-builder.hpp index 798816811..d0675474b 100644 --- a/src/steam/engine/weaving-pattern-builder.hpp +++ b/src/steam/engine/weaving-pattern-builder.hpp @@ -250,8 +250,8 @@ namespace engine { static constexpr SizMark sizMark{}; static constexpr uint FAN_I = PROT::FAN_I; static constexpr uint FAN_O = PROT::FAN_O; - + using Param = typename PROT::Param; ///////////////////////////OOO integrate here a partial-closure-helper using TypeMarker = std::function; using ProviderRef = std::reference_wrapper; diff --git a/tests/core/steam/engine/node-builder-test.cpp b/tests/core/steam/engine/node-builder-test.cpp index 9c4916dee..14b0dcc94 100644 --- a/tests/core/steam/engine/node-builder-test.cpp +++ b/tests/core/steam/engine/node-builder-test.cpp @@ -23,6 +23,7 @@ #include "lib/test/diagnostic-output.hpp" #include "lib/time/timequant.hpp" #include "lib/time/timecode.hpp" +#include "lib/iter-explorer.hpp" #include "lib/symbol.hpp" #include @@ -31,6 +32,7 @@ using lib::Symbol; using std::string; using std::array; +using lib::explore; using lib::time::Time; using lib::time::QuTime; using lib::time::FrameNr; @@ -62,6 +64,7 @@ namespace test { build_Node_fixedParam(); build_Node_dynamicParam(); build_Node_adaptedParam(); + build_Node_closedParam(); build_connectedNodes(); build_ParamNode(); } @@ -190,6 +193,35 @@ namespace test { } + /** @test build a node and partially close (≙ predefine) some parameters, + * while leaving other parameters open to be set on invocation + * through a parameter-functor. + * @remark it is quite common that processing functionality provided by an + * external library exposes both technical and artistic parameters, which + * leads to the situation that technical parameters can be predetermined + * and configured to a fixed value, while artistic parameters remain open + * for control by the user, either as a fixed setting (e.g. colour balance) + * or even a dynamic control by an automation function). + */ + void + build_Node_closedParam() + { + using Params = std::tuple;//array; + auto procFun = [](Params params, uint* out) { auto [v1,v2,v3,v4,v5] = params; *out = v1+v2+v3+v4+v5; };//explore(params).resultSum(); }; + auto autoFun = [](Time nomTime){ return FrameNr::quant (nomTime, SECONDS_GRID); }; + ProcNode node{prepareNode("Test") + .preparePort() + .invoke ("fun()", procFun) + .closeParamFront (1,2,3,4) + .attachAutomation (autoFun) + .completePort() + .build()}; + + Time timeOfEvil{5555,0}; +SHOW_EXPR(invokeRenderNode(node,timeOfEvil)); + } + + /** @test build a chain with three connected Nodes * - have two source nodes, which accept a parameter * - but configure them differently: one gets a constant, diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 9eb12a090..6ea9d1415 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -58763,7 +58763,7 @@ - + @@ -58793,7 +58793,8 @@ - + + @@ -58836,6 +58837,86 @@ + + + + + + + +

+ definiert in <functional>, ca. Zeile 280 +

+ +
+ + + + + + + + +

+ alle Initialiser werden per perfect-forwarding genommen +

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

+ im Placeholder-Typ wird die Index-Position gespeichert +

+ +
+
+
+ + + + +

+ und zwar als Signatur Fun(Arg....); +

+

+ dabei sind die Arg entweder exakt die Typen, wie sie gebunden und damit im Binder gespeichert sind, oder die Placeholder, die in ihrem Typ die Position aus dem verbleibenden Arg-Tupel codiert halten. +

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

+ ....durch std::apply und std::invoke +

+ + +
+
+ + + + + + + @@ -58892,11 +58973,45 @@

- wenn man ein Objekt wie einen unique_ptr oder einen vector per value  an den partial-application-Functor übergibt, dann wandert die Ownership zu der Kopie, die in der Closure gespeichert ist. Mit dem ersten Aufruf der Funktion wander die Ownership weiter zum jeweils gebunden Funktions-Argument, und hinterläßt in der Closure ein »verbrauchtes Objekt«. Weitere Aufrufe der gleichen Funktion bekommen dann dieses potentiell invalidierte Objekt. + wenn man ein Objekt wie einen unique_ptr oder einen vector per value an den partial-application-Functor übergibt, dann wandert die Ownership zu der Kopie, die in der Closure gespeichert ist. Mit dem ersten Aufruf der Funktion wander die Ownership weiter zum jeweils gebunden Funktions-Argument, und hinterläßt in der Closure ein »verbrauchtes Objekt«. Weitere Aufrufe der gleichen Funktion bekommen dann dieses potentiell invalidierte Objekt.

+ + + + + + + + +

+ std::bind funktioniert nur mit copy-konstruierbaren  Objekten +

+ +
+ + + +

+ nur die frei-bleibenden Argumente können move-Semantik haben. Und wenn man definitiv eine Referenz im Binder speichern möchte, so muß man einen std::reference_wrapper abliefern. +

+ +
+ +
+ + + + +

+ std::bind wendet intern ganz ausdrücklich std::decay_t  an, und speichert das sich ergebende Value-artige Objekt +

+ +
+
+
@@ -105572,9 +105687,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

das geht, weil durch das constexpr-if die Funktion jedesmal erneut instantiiert wird, mit dann jeweils anderem deduziertem auto-Returntyp @@ -105599,6 +105712,254 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + +

+ ...denn im Grunde sind alle diese Bindings mit Lambdas relativ banal zu formulieren — es ist halt bloß relativ technisch, versaut den Code und erfordert vom Benutzer viel Präzision und Beachten von feinen Punkten. Da »der Benutzer« auf absehbare Zeit ich selbst bin, ist das — Määh. Ja ich kann das. Wääh +

+ + +
+
+ + + + +

+ Das klingt nach einer Lösung, die billig zu haben wäre. Es sind ein paar Zeilen recht technischer Code im Node-Builder, der ohnehin in diesem Bereich „nicht mehr schön“ ist. Kann man also in jedem Fall mal einbauen — mein einziger Zweifel betrifft die Nützlichkeit und Relevanz; warum man ausgerechnet ein einziges erstes Argument schließen wollte, will sich mir nicht recht erschließen +

+ + +
+
+ + + + +

+ Das ist pre-C++11-Code, nur oberflächlich modernisiert, und bisher nur in einem Bereich testhalber verwendet (für Proc-Commands). Grundsätzlich möchte ich diesen Code aber erhalten, nicht loswerden. Risiko: es gibt wohl einige »Untiefen« — es ist nicht klar, wo Heap-Allokationen stattfinden, und auch der Umgang mit non-copyable oder move-only-Typen ist nicht wirklich ausgeleuchtet. Wahrscheinlich deshalb habe ich vor wenigen Jahren die beiden convenience-Wrapper vom alten Implementierungs-Framework abgekoppelt und direkt per Lambda implementiert — und dann seither doch nicht mehr benutzt (bestätigt die Zweifel bezüglich partieller Applikation nur eines Arguments, das ist dann doch ehr so ein Haskell-Ding) +

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

+ die Arität muß ermittelt werden, +

+

+ denn std::make_tuple ist ein Template +

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

+ ...weil wir den Processing-Functor jeweils aus dem Prototypen kopieren, dann aber in der Anwendung des Binders nochmal das gespeicherte Element ins Ergebnis kopieren +

+ + +
+ + + + +

+ zwar könnte man auf die Idee kommen, den Adapter hinter den Parameter-Functor zu setzen. Das läßt sich aber in dem hier gewählten cross-Builder-API nicht realisieren, da der Parameter-Functor sofort auf Kompatibilität mit dem Parameter-Tuple-Typ geprüft wird, und obendrein auch sofort dahingehend festglegt wird, daß er ein TurnoutSystem& als Argument bekommt. Ein schrittweises Aufbauen einer Funktor-Kette ist also nur beim Processing-Functor möglich. Es sei denn, man würde eine wesentlich elaboriertere Darstellung finden können, bei der ein Gesamt-Functor-Typ für die Parameter irgendwo in den Template-Argumenten aufgebaut wird. +

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

+ ...da wir durch das Anwenden auf einen umgebauten Processing-Functor schwenken; deshalb kann auf diesen sodann erneut eine Adaptierung angewendet werden — selbst wenn diese dann schließlich das Parameter-Tupel complett schließt (denn ohne Parameter-Functor erfolgt der Aufruf implizit mit einem default-konstruierbaren Parameter-Tupel, und das leere Tupel ist uneingeschräntk default-konstruierbar) +

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

+ RebindVariadic geht nur für Klassen-Templates +

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

+ muß eingangsseitig Einzelwert oder Aggregatwert entgegennehmen +

+ +
+
+ + + + + +

+ ...denn wir müssen den generierten Binder in diesem Funktor speichern +

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

+ wenngleich er auch eine std::function liefert — das würde ich temporär akzeptieren, bis die function-closure-Tools umgebaut sind (⟹ Ticket!) +

+ +
+
+ + + + +

+ das heißt, doch auch die Builder-Funktion selber schreiben — vermutlich eingebettet in ein Builder-Konfigurator-Template +

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