Object.observe and observing "computed properties"

Steve Sanderson flares at gmail.com
Wed Aug 29 03:09:51 PDT 2012


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.

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.

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!


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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120829/213076f1/attachment-0001.html>

More information about the es-discuss mailing list