avoiding side-effects

62 views
Skip to first unread message

Brian Craft

unread,
Aug 13, 2013, 7:14:18 AM8/13/13
to fla...@googlegroups.com
The flapjax paper makes a large point about the difficult of programming with side-effects. However I'm having trouble coming up with usable real-world patterns in flapjax that aren't side-effecting.

As one example, consider an animation loop. Changes arrive on an event stream. When the first one arrives, a draw should be scheduled for some time in the future (e.g. with requestAnimationFrame). Changes should then accumulate until the draw event fires, at which point all the accumulated changes should be drawn.

Is there a simple way to do this in flapjax? There seems to be a difficulty in expressing stateful things like "a draw event has been queued". The "world" pattern can be made to work, but then it's essentially the same as the imperative solution, and relies on side-effects.

Here's a solution using observer pattern:

function initialize(opts) {
    var queued = false,
        changes = {};

    function dodraw() {
        draw(changes);
        queued = false;
        changes = {};
    }

    opts.data_source.on('change', function (data) {
        if (!queued) {
            window.requestAnimationFrame(dodraw);
            queued = true;
        }
        _.extend(changes, data);
    });
}

And here's a flapjax solution using the "world" pattern:

function initialize(opts) {
    var blitE = fj.receiverE();

    function accumulate(state, data) {
        if (!state.queued) {
            window.requestAnimationFrame(blitE.sendEvent);
        }
        return {queued: true, changes: _.extend({}, state.changes, data)};
    }

    function dodraw(state, _) {
        draw(state.changes);
        return {queued: false, changes: {}};
    }

    worldE({queued: false, changes: {}},
            [[opts.data_source, accumulate], [blitE, dodraw]]);
}

This is pretty much the same thing, only more obtuse. Nothing consumes the worldE stream. Draw events happen as a side-effect of constructing the stream.

Alexander Yuryev

unread,
Oct 23, 2013, 5:30:22 AM10/23/13
to fla...@googlegroups.com
I've tried to avoid using state variables and use more event combinations. Here is working but not very-end-beautiful code:
            dataE - changes event stream
            frameE - browser animation frame event
            sendRequest - animation frame request

            var currentCollectedDataB = mergeE(dataE, frameE).collectE([], function(e, v) {
                // here some ugly place, but for now I have no idea how to avoid it
                // Data event will have e=non-null change, frame event will have e=null,
                // but we can't clear just when e=null because drawChanges has less priority and
                // will get [] in such case
                // so we store null in array and if the last element is null, clear all elements
                // ugliness: we have to filter out null elements in future
                return v[v.length-1] == null ? [e] : v.concat(e);
            }).startsWith([]);

            var drawE = snapshotE( //main idea: on every animFrame snapshot currentCollectedDataB
                frameE,
                currentCollectedDataB
            );

            drawE.mapE(function(changes){
                drawChanges(filter(function(v){return v!=null}, changes)); // ugly place
                dataE.onceE().mapE(sendRequest); //request frame on the first change after the draw
            });
            sendRequest(); //first frame will be dump to activate loop


correct me please if your get better idea. Full html code is attached.

вторник, 13 августа 2013 г., 15:14:18 UTC+4 пользователь Brian Craft написал:
anim.html
Reply all
Reply to author
Forward
0 new messages