Object.observe and observing "computed properties"

Alex Russell slightlyoff at google.com
Thu Aug 30 10:44:17 PDT 2012

On Wed, Aug 29, 2012 at 11:09 AM, Steve Sanderson <flares at gmail.com> wrote:

> Hey
> Thanks very much for your detailed response! I totally agree that the
> question can be distilled down to "deciding when [a] function should be
> re-evaluated", and that two approaches are "2a: requiring function author
> to declare [dependencies]" or "2b: attempt to discover [dependencies]".
> To clarify why Knockout/Batman/CanJS/etc developers make use of dependency
> detection (and why for them it's necessary for it to be integrated into
> observability), there are two main scenarios:
> [1] Computed properties whose output depends on observable state. Example:
> *    this.grandTotal = function() {*
> *        var result = 0;*
> *        for (var i = 0; i < this.items.length; i++) { result +=
> this.items[i].price; }*
> *        return result;*
> *    };*
> Knockout developers are used to this sort of thing updating automatically
> whenever you modify the price of any item, or when you add or remove items
> to the array. It would be very inconvenient to have to somehow declare
> dependencies manually (2a) - I'm not even sure what kind of syntax or
> mechanism you could use when the set of dependencies changes over time.
> That leaves option (2b) which works great, as long as dependency detection
> is built into observability.

I'm not sure that's true. Side-effects are a real pain and it seems to me
that there's going to be some practical advice at the bottom of any of
these systems that says, in effect, "don't do things we can't understand".
That sort of advice is likely to be backed up with tools to assist you in
helping developers understand those limits; say transpiler passes that
analyze the dependencies in a function.

It strikes me that this is at some level a question of how deep your
analysis of the target function is willing to go. So far the examples
dependencies are only on in-scope objects inside a computed property's
generator. But what about methods called there that might have inputs that
change? How deep does the propagation go?

It seems that the implicitness of this strategy implies that some computed
properties will be *always* marked "regenerate" as it'll be
simpler/easier/faster than doing something more sophisticated.

As Rafael correctly points out, some functions may return nondeterministic
> values, or values that continually change even given the same inputs (e.g.,
> new array instances). In practice that doesn't cause any downside in real
> KO applications - you just receive the latest return value and work with
> it. It's true that evaluator functions that mutate their own dependencies
> are potentially bad (in the worst case, stack overflow), but in practice no
> worse than regular recursive functions.
> [2] Functions whose effect varies based on observable state. Example:
> *    function displayPersonInfo() {*
> *        $(".name").text(myModel.name.first);*
> *        $(".age").text(myModel.age);*
> *        $(".isTall").val(descriptions.isPersonTall(myModel.id));*
> *        console.log("Refreshed display for person " + myModel.id);*
> *    }*
> *    someLibrary.autoRefresh(displayPersonInfo);*
> The facility here is being able to take action when one of a set of things
> change. This is a core part of why it's easy to write custom binding logic
> in KO - you simply provide a callback that accesses arbitrary data and does
> something (without having to declare dependencies or subscribe/unsubscribe
> to anything), and you know it will be re-run when necesssary.
> ----
> Overall, I've no wish to derail the Object.observe proposal, and fully
> accept that dependency detection might end up being out of scope for it.
> However if the ES recommendation will end up being "solve this problem
> through convention, not language support", I'd love to have a sense of what
> kinds of conventions (examples, preferably) we would actually be
> recommending and how they would offer a similar level of convenience to
> dependency detection.

I don't think they will, frankly. The best of them will re-create
dependency detection via compiler. The less aggressive may simply force
enumeration of dependencies or create conventions which cause
particular properties to be observed through participation.

> François's proposal of a "make bindable" function is certainly
> interesting, and I wonder to what extent it could tie in with
> Object.observe. Perhaps you could have some function that meant "run this
> callback, and use Object.observe to set up a subscription to anything
> bindable that the callback touches". If so that would have pretty much
> exactly the right semantics for the scenarios I'm considering, and the fact
> that objects aren't bindable by default would mitigate perf costs.
> Thanks again to Rafael for leading the charge on this and making web
> development better in the long run!
> Steve
> On Wed, Aug 29, 2012 at 6:28 AM, Rafael Weinstein <rafaelw at chromium.org>wrote:
>> Steve Sanderson (author of the excellent KnockoutJS framework) and
>> François REMY both raised the issue of observing computed properties
>> WRT to the Object.observe() proposal.
>> Having thought about this problem while helping to author
>> Object.observe, my thoughts are as follows:
>> First, I think you need to look at the problem the right way.
>> "Observing computed properties" is kind of non-sensical. What this
>> translates to is "observe when the *return value* of an anonymous
>> function invocation *will be* different from the last invocation".
>> I think the only reasonable way to look at it is this: imagine that a
>> *data property* can be defined whose value can only be set by
>> assigning it the return value of a given function. Observing when a
>> data property changes value is trivial. The problem becomes: deciding
>> when the function should be re-evaluated.
>> Looked at like this, the first thing that becomes obvious is that
>> there are a class of functions which should never be used: those
>> functions that can "spontaneously" change value. e.g.
>>   var i = 1;
>>   function getVal { return ++i; }
>>   function getVal2 { return Math.random(); }
>>   function getVal3 { return myElement.offsetHeight; }
>> [I actually think at this point it becomes clear that, because the
>> task at hand isn't solvable in general, it's not appropriate to
>> include support for it at the language level, but lets continue].
>> That said, those functions:
>> -whose output is dependent only on the value of a discrete set of
>> inputs, (i.e. are stateless)
>> -don't modify their inputs
>> -will always return the same output value for the same set of inputs
>> can be sensibly used for computed property functions. It's worth
>> noting that even this can be easy to get wrong. For example, many
>> webdevs might not realize that
>>   var firstName = 'Rafael';
>>   var lastName = 'Weinstein';
>>   function getVal() { return [firstName, lastName]; }
>> doesn't meet this criteria as its return value is always a different
>> Array object.
>> Assuming that you've been careful to pick an appropriate function,
>> there are two approaches to knowing when to reevaluate it:
>> 1) Dirty-checking: Immediately before each time you "need" the value
>> of the dependent property.
>> 2) Dependency observation: When one or more of its inputs have changed
>> value.
>> At this point, you have only trade-offs. (1) Is potentially expensive
>> and hard to do at exactly the right time, but (2) requires having
>> proper knowledge of the function's inputs.
>> Obtaining the set of inputs for (2) can be done in two ways:
>> 2a) Require the function author to declare them.
>> 2b) Attempt to discover them by running the function and observing
>> which inputs it accesses.
>> (2a) requires some careful attention on the part of the function
>> author, so in some sense, if (2b) were possible, it would be ideal.
>> This brings us to what KnockoutJS does and what François proposed, so
>> let's consider it.
>> The first problem is what I discussed above, that creating an
>> *appropriate* function is potentially tricky and/or hard to
>> understand, and there isn't any way to statically determine if a
>> function is or is not appropriate.
>> The second problem is that doing this risks "discovering" inputs that
>> aren't really inputs at all. In other words, the function, just be
>> being invoked happens to "touch" a wide swath of objects, even though
>> they aren't dependencies of the function. This is bad because it would
>> cause the system to "observe" these objects, which, given modern VMs,
>> will cause them to "de-optimize" and become slower to access
>> Thus, offering language-level support for (2b) puts developers in the
>> risky situations of authoring computed property functions which may
>> A) not fire their notifications, even though they appear to have "changed
>> value"
>> B) become a "go-slow" button for their entire application
>> ...with no good recourse to discover why either is happening.
>> Note that François's proposal included a mitigation of (B), in that
>> you need to whitelist objects as potential discoverable dependencies.
>> This helps some with the risk of "discovering too many" dependencies,
>> but it also risks "not discovering enough" dependencies, which becomes
>> problem (A) again.
>> ----
>> Thus, what we have is a problem which is really solved through
>> convention, not through an abstract solution, and thus the most
>> sensible thing to do is leave it to authors to make trade-offs for
>> themselves about the pros & cons of the various approaches and the
>> conventions they imply.
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120830/a6ec0914/attachment-0001.html>

More information about the es-discuss mailing list