Object.observe and observing "computed properties"

Rafael Weinstein rafaelw at chromium.org
Tue Aug 28 22:28:46 PDT 2012


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.


More information about the es-discuss mailing list