FLUID-6580: Thoughts on "integration constant" lenses to implement toggle interactions

Metadata

Source
FLUID-6580
Type
Improvement
Priority
Major
Status
Closed
Resolution
Fixed
Assignee
Antranig Basman
Reporter
Antranig Basman
Created
2020-11-13T10:48:11.103-0500
Updated
2024-07-22T10:35:28.432-0400
Versions
N/A
Fixed Versions
  1. 4.0
Component
  1. Framework

Description

Following FLUID-6570 and FLUID-6576, we have for a while sketched an idea for a form of "integral relay" allowing the expression of a click-to-toggle interaction. Following the sketches of materialised DOM relay rules in FLUID-6570, we would like to write a rule like

modelRelay: {
            source: "{that}.model.dom.button.clicked",
            target: "{that}.model.enabled",
            singleTransform: "fluid.transforms.toggle"
        }

This immediately extends the contract for model transformations in a dramatic way. The original conception was for what could be called an "integration constant" relay - bearing in mind that the value in the materialised DOM field {that}.model.dom.button.clicked was an integration of a series of differential events (clicks), the job of the lens would be, at the point the relay is set up, to take up the slack of the constant of integration resulting from this integration process as an internal "phase".

This idea may have stemmed from a creative misunderstanding of a seminar presented at University of Boulder on Pierce-ist lenses in which it seemed that these kinds of putbacks were intended to be honoured by kinds of "pockets" in which lenses held parts of previously imaged state. However, consulting the actual Pierce-ist diagram that we have been reproducing in talks for many years (on page 6 of https://www.seas.upenn.edu/~harmony/manual.pdf ) this is clearly not the way they are actually meant to work. As anyone might expect from this tradition, lenses are intended to be functional - as expressed in this idiom, the putback action is achieved by the lens accepting an additional argument, being a pure function of the "old source", the "updated target/view", and yielding the "updated source".

This Pierce-ist view is clearly what we were intending to follow when we wrote point ii) on FLUID-6576. However, looking at the machinery that we have in the model relay system, as well as our actual use case, this route appears prohibitive. In the attached diagram, we show the expected state updates for two expected initial values of the target.

Correlating this with the traditional lens diagram, we position the lossy direction of the lens as aiming to the right. However, our update isn't in the traditional direction because in our case it is the source which is getting updated again - therefore we need to flip the direction of all the arrows attached to the "put" lens. So what we require access to in this case in the Pierce-ist view is the old version of the target, and not the source. However, in order to be able to compute what needs to be output, we need to be able to determine what the change in source value has actually been. There is no provision for this and would require the lens to be improbably be made a function of THREE things, the old source, the updated source, and the old target.

A big mismatch between our use of the lens idiom in model relay and Pierce-ism is the fact that we allow relay updates to slosh around in the graph of models in an essentially unordered way. All we track is the initial snapshot of the entire model state, and its "in-progress" value, and keep propagating until the graph stabilises. This implies that lenses may be activated numerous times along the same leg in the same transaction, if it's determined there is a change in their source.

[Note - an idea for making this process much more efficient for FLUID-6396 is to always pass the old source to a lens so that it can return it unchanged if it is an acceptable part of its inverse image - this was already called for in FLUID-5337]

Well - perhaps it's not so onerous to supply old source and old target too - let's investigate

Attachments

Comments

  • Antranig Basman commented 2020-11-16T07:26:16.876-0500

    Last recorded form of "pocket lenses":

    /** Toggle transformer which maps an increasing integer count into the space true/false - suitable for mapping
         * e.g. a stream of click counts onto a toggle state.
         */
    
        fluid.defaults("fluid.transforms.toggle", {
            gradeNames: ["fluid.standardTransformFunction", "fluid.pocketLens"],
            invertConfiguration: "fluid.transforms.toggle.invert",
            relayOptions: {
                forward: {
                    excludeSource: "init"
                },
                backward: {
                    includeSource: "init"
                }
            }
        });
    
        /** @param {Number} source - Numeric value to be converted
         * @param {Boolean} target - Current value in target 
         * @param {Object} pocketHolder - State allocated to hold "pocket" by previous operation of other leg of lens
         * @return {Boolean} Updated target value 
         */
        fluid.transforms.toggle = function (source, target, pocketHolder) {
            return (source + pocketHolder.phase || 0) % 2 === 1;
        };
    
    
        fluid.transforms.toggle.invert = function (transformSpec) {
            transformSpec.type = "fluid.transforms.inverseToggle";
            return transformSpec;
        }
    
        fluid.defaults("fluid.transforms.inverseToggle", {
            // This transform is only expected to arise as the inverse of fluid.transforms.toggle and so it does not
            // produce an inverse.
            gradeNames: ["fluid.standardTransformFunction", "fluid.pocketLens"]
        });
    
        /** @param {Boolean} source - Boolean toggle value to be converted 
         * @param {Number} target - Numeric count currently in target
         * @param {Object} pocketHolder - State allocated to hold "pocket" by previous operation of other leg of lens
         * @return {Number} Updated target count
         */ // We expect this leg to only run during startup to acquire phase, as per relayOptions of "fluid.transforms.toggle"
        fluid.transforms.inverseToggle = function (source, target, pocketHolder) {
            target = target || 0;
            var updatedTarget;
            // Boolean value corresponding to current target - "source" should match this
            var pullback = fluid.transforms.toggle(target, pocketHolder);
            // Store phase which will cause match
            pocketHolder.phase = pullback === source ? 0 : 1;
            // We never update the target
            return target;
        };