1
\$\begingroup\$

Observer implementation that leverages both behaviours observing a variable and observing for a value. Supports subscribing and withdrawing observers.

var Diffuser = (function() {
    return { supply: function() {
        return (function() {
            var diffusees = [];
            var oneTimeDiffusees = [];
            
            var withdraw = function (value) {

                var once = function(values) {
                    var index = values.indexOf(value);
                    if ( index > -1 ) {
                        values.splice(index, 1);

                        return true;
                    }

                    return false;
                };
                var remover = once;

                return { from: function(values) {
                                   return remover(values);
                         }
                       , entierly: function() {

                                       remover = function(values) {
                                                     var index = values.indexOf(value);
                                                     if ( index == -1 ) { return false; }
                                                     for ( var i = 0; i < values.length; i++ ) {
                                                         if ( values[i] === value ) {
                                                             values.splice(i, 1);
                                                         }
                                                     }
                                                     return true;
                                                 };

                                       return this;
                       }
                     , once: function() {

                                 remover = once;

                                 return this;
                     }};
            };
            
            return {
                diffuseTo: function(diffusee) {
                    diffusees.push(diffusee);
                    return true;
                }
                , oneTimeDiffuseTo: function(diffusee) {
                    oneTimeDiffusees.push(diffusee);
                    return true;
                }
                , withdraw: function(diffusee) {
                    return withdraw(diffusee).from(diffusees);
                }
                , withdrawOneTimeDiffusee: function(diffusee) {
                    return withdraw(diffusee).from(oneTimeDiffusees);
                }
                , diffuse: function(diffused) {
                    var diffuseFunction = function() {
                        diffusees.forEach( (diffusee) => { (function() { diffusee(diffused); })(); });
                    };
                    var oneTimeDiffuseFunction = function() {
                        for ( var i = 0; i < oneTimeDiffusees.length; i++ ) {
                            var diffusee = oneTimeDiffusees.pop();
                            if ( !(function() { return diffusee(diffused); })() ) {
                                oneTimeDiffusees.push(diffusee);
                            }
                        }
                    };
                    
                    setTimeout( function() { 
                        
                        diffuseFunction();
                        oneTimeDiffuseFunction();
                        
                    }, 0);
                }
            };
        })();
    } };
})();

Usage example:

var crunch = (function() {

    var oneTimeListener = (function() {
        var aLetter = “a”;
        var theCodeToRunForALetter = () => {};
        return (listenedValue) => { 
            if ( listenedValue != aLetter ) { return false; }

            theCodeToRunForALetter();

            return true;
        };
    } )();

    var listener = (value) => {};

    var observed = Diffuser.supply();
    observed.diffuseTo(listener);
    observed.diffuseTo(oneTimeListener);

    return (toCrunch) => {
        Array.from(toCrunch).forEach( (letter) => { observed.diffuse(letter); } );
    };
})();

crunch("some text to crunch");
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

be generic

        var aLetter = “a”;
        var theCodeToRunForALetter = () => {};

The library generically implements Observer pattern over the elements of an array. Consider presenting this function in a more generic way by renaming "letter" to "element".

Alternatively, consider offering a bit of scaffolding "in between", so the example may easily consume an "element" abstraction that immediately grounds out to a character or "letter".

You could offer a more instructive function than the rather hypothetical theCodeToRunForALetter(), perhaps something that counts vowels. OIC, that use case would require a more flexible predicate. Maybe something that counts occurences of "a".


quadratic complexity

My reading of the spec is that .splice() is required to take O(n) linear time.

                    var index = values.indexOf(value);
                        ...
                        values.splice(index, 1);

I think you're computing once one time for each element of values? In which case we're O(n^2) quadratic in number of values. Consider overwriting with a tombstone sentinel instead.

Potentially quadratic, depending on whether we have lots of repeated values, such as boolean values:

                                             for ( var i = 0; i < values.length; i++ ) {
                                                     ...
                                                     values.splice(i, 1);

If the caller can expect O(n) linear behavior for typical inputs, spell out in the documentation what those inputs would look like.


one time

            var oneTimeDiffusees = [];

Sorry, I didn't get that concept. Citing an URL would go a long way toward explaining its motivation.

There seems to be some review context missing, at least for the example call, but hey, we'll take what we can get and run with it. Certainly the crunch() example has pedagogic value that helps motivate the library -- thank you for that.

As an app developer calling into this library, I could use some more clarity on what I'm responsible for passing in and what I can expect of the library's behavior. Partly this is due to the "hello world" example needing more lines of code than I would anticipate.

                       , entierly: function() {

nit, typo: Please use standard spelling of entirely.


conventional terminology

The verb "diffuse" is fine, you can certainly leave it as-is.

But consider renaming to "subscribe", more in line with the literature. One encounters "subscriber" far more often than "diffusee". The similarity between "diffuser" and "diffusee" seems unhelpfully distracting.


documentation

The explanatory prose accompanying this submission is very nice. It does not appear in the source code, and it absolutely should.

Several of the interior functions need comments, including returned functions.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ diffuse - and its conjugations - is far from fine. It actively thwarts comprehension. Where is the "observer/subscriber?" Where is a/the/any pattern? diffuseness is a noun but how we get from "to pour out and cause to spread, as a fluid" to a subscriber or observable is baffling. I should stop and re-read but I'm still on the first line. The Usage Example looks like missing implementation not a use case. \$\endgroup\$
    – radarbob
    Commented Aug 29, 2023 at 21:04
  • \$\begingroup\$ Yeah, I guess I was trying too hard to be kind, to not assign action items prior to merge-to-main. I agree that it thwarts comprehension. \$\endgroup\$
    – J_H
    Commented Aug 29, 2023 at 21:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.