This is a request for comments as described at http://
wiki.openlaszlo.org/Enhancement_Proposals. If this proposal is
accepted after community discussion, a task to implement it will
be added to JIRA. Acceptance doesn't imply a commitment to
schedule it against any specific milestone; community interest and
the availability of contributors will help to determine this.
It addresses a problem that has come up in the use of the reusable
components, within the OpenLaszlo canaries: the Product
Development and Professional Services groups at Laszlo Systems.
A Non-Problem (for comparison)
Consider a class:
<class name="baseclass">
<method name="f">return 1;</method>
...
Consider the case where an instance or subclass of baseclass is
required, but with an f method that returns 2 instead of 1. (This
is an obviously contrived example, but cases like this one come up
all the time.) This is a solved problem, both in OpenLaszlo and
other prototype and OO systems. The instance or subclass
definition can override the method:
<class name="myclass" extends="myclass">
<method name="f">return 2;</method>
(This does require either a little bit of luck or a little bit of
foresight, in that the superclass has to access the overridden
behavior through a discrete method. It wouldn't be so simple if
the '2' were a constant in the middle of a 100-line method, or
twenty occurrences of ten constants (2, 4, 20) in the middle of a
number of methods or attributes. However, things often things work
out; I'd say that figuring out how fine grained to make methods
involves engineering tradeoffs among constraints, but that the
language-level mechanisms to make the tradeoff are present.)
The Problem
Case 1: Extending a child's attributes
But now consider a class:
<class name="baseclass">
<view name="top" height="20" width="100%" bgcolor="red"/>
...
And consider the case where an instance or subclass is required,
but the "top" child view of this subclass should have a height of
40 and a background color of "blue".
And keep in the back of your mind that "baseclass" is standing in
for a hundred-line class, so you might not want to simply copy it
if baseclass is still under active development and you may want to
update to a newer version in the future.
Workaround A
The problem is that baseclass doesn't contain enough extension
points. It could have been written thus:
<class name="baseclass">
<attribute name="topHeight" value="20"/>
<attribute name="topColor" type="color" value="red"/>
<view name="top" height="$once{parent.topHeight}" width="100%"
bgcolor="$once{parent.topColor}"/>
...
Then a subclass could do this:
<class name="myclass" extends="baseclass" topHeight="40"
topColor="blue"/>
There are two problems with this workaround:
- First, the new baseclass definition is larger, slower to
initialize, and less readable. It's not clear that this rewrite
would be acceptable to the maintainers of baseclass, to the other
clients of baseclass, or even (given the performance penalty) to
this client.
- Second, in the scenario above, baseclass wasn't defined that
way. If the author of the client of baseclass owns the source to
baseclass, this isn't an issue; otherwise, she has to choose to
fork baseclass, with the increased maintenance burden going
forward. This isn't an issue for one-off classes; it is for
interproject dependencies, as for an application written by
someone outside the OpenLaszlo team (let's see, that's pretty much
every application), that depends on the standard components that
are maintained by the OpenLaszlo team.
Workaround B
Another solution is to use code in the subclass to modify the
baseclass's structure:
<class name="myclass" extends="baseclass">
<method event="oninit"> <!-- or onconstruct? warning: untested
code -->
top.setAttribute('height', 40);
top.setAttribute('bgcolor', blue);
</method>
This has some problems too:
- Yecch!
- It adds to the size and initialization time of myclass.
- It might not even work, if baseclass does initialization-time
computation that depends on access to the parameters.
Workaround C
If every subclass or instance of baseclass should be overridden in
the same way, then the new compile-time CSS proposal can be used
to modify the definition of baseclass. This doesn't help if some
subclasses of baseclass should be overridden and others shouldn't,
or if they should be override in different ways.
Case 2: Replacing a child
Also, none of the workarounds above addresses another use case,
where a subclass requires the specialization of one of the
children of the superclasses. (This is akin to covariance in
method definitions.) For example, myclass requires a 'top' of
myview, so that the resulting structure looks like this:
<myclass>
<mytop name="top">...
even though baseclass (which myclass extends) defines a 'top' with
class <view>, not <mytop>.
This pattern comes up with classes that are designed to work
together, and be extended together, e.g.:
<class name="basetop">...</class>
<class name="baseclass">
<basetop name="top">...
<class name="mytop" extends="basetop">...</class>
<class name="myclass">
<!--- how do we get mytop in here? -->
Workaround D
A solution for this use case is to parameterize the master class
with the name of the support class, and let it construct the class
in script:
<class name="baseclass">
<attribute name="topclass" type="string" value="topclass"/>
<method name="onconstruct">
this.top = new eval(this.topclass)(this, {options}) // or
something like this
This shares a number of the problems with the workarounds for the
previous use case, above.
Proposals
What follows are two alternative proposals, that differ only in
their syntax. Both proposals is define new attributes which can
be used to annotate the use of an element to extend or replace an
element in a base class that the annotated element's parent
extends. These attributes determine whether an element within an
instance or class definition: (1) is added to the structure of the
class definition (as a child or, in the case of placement and for
the purpose of the view containment hierarchy, a descendant); (2)
extends an element within the class definition; or (3) replace
elements within the class definition. (1) is the default, and the
only currently supported functionality. (2) and (3) are the first
and second use cases, respectively.
For purposes of these proposals, assume the definition:
<class name="baseclass">
<view name="top" height="20" width="100%" bgcolor="red"/>
The intent of the definitions is this:
- That the extension of the "top" view in the examples below will
cause <baseclass> to behave, for the purpose of the element that
contains the replacement syntax only, as though it had been
defined as:
<class name="baseclass">
<view name="top" height="40" width="100%" bgcolor="blue"/>
- That the replacement of the "top" view in the examples below
will cause <baseclass> to behave, for the purpose of the element
that contains the replacement syntax only, as though it had been
defined as:
<class name="baseclass">
<mytop/>
Also, all the examples show only a single annotated child element,
but the intent is that several elements could target different
children of the same definition.
Definitions: target class, target element, overriding element
The target class is the class that is being subclassed or
instantiated. The class use of the class is the class or instance
that extends or instantiates it, respectively. The target element
is the element whose name is the value of the @extends or
@replaces attribute (in proposal A) or the @name attribute (in
proposal B), and that is an immediate child of the targeted
class. The overriding element is the element that contains the
@extends or @replaces attribute (in proposal A) or the @override
attribute (in proposal B).
For example, in the following program, the class defined in (1-3)
is the target class for the class use in (4-6). In this case, the
instance declared at (2) is the target element for the overriding
element in (5). The class defined in (1-3) is also the target
class for the class use in (7-9) and the overriding element in (8).
1 <class name="baseclass">
2 <view name="top">
3 </class>
4 <class name="myclass" extends="baseclass">
5 <view extends="top">
6 </class>
7 <baseclass>
8 <view extends="top">
9 </baseclass>
Definition: extend
An element A extended by element B has the interpretation of an
element identical to A, except that it also contains the each
attribute of B (instead of the attribute of A, where these have
the same name), and the children of the extended element are the
children of A followed by the children of B.
For example, the element defined in (1-3) below, extended by the
element defined in (4-6) below, has the interpretation of the
element in (7-10).
1 <view x="10" y="20">
2 <view name="a"/>
3 </view>
4 <view y="30" bgcolor="red">
5 <view name="b"/>
6 </view>
7 <view x="10" y="30" bgcolor="red">
8 <view name="a"/>
9 <view name="b"/>
10 </view>
This is intended to be the same definition of extension by which
the class defined by <class name="B" extends="A"> extends the
class named by A, with the exception that it does not create a
binding for 'classroot'.
Proposal A: @extends and @replaces attributes
Add the @extends and @replaces attributes to views and other nodes
(elements that correspond to classes that are subclasses of LzNode).
If the @extends attribute is present, the overriding element
extends the target element for purposes of the class use. If the
@replaces attribute is present, the overriding element replaces
the target element for purposes of the class use.
It is an error that is signaled at compile time and/or runtime if
one of these conditions holds:
- Both an @extends and a @replaces attribute are present.
- The target element does not exist.
Case 1: extending a class child
Subclass:
<class name="myclass" extends="baseclass">
<view extends="top" height="40" bgcolor="blue">
Instance:
<baseclass>
<view extends="top" height="40" bgcolor="blue">
Case 2: replacing a class child
Subclass:
<class name="myclass" extends="baseclass">
<mytop replaces="top">
Instance:
<baseclass>
<mytop replaces="top">
Proposal B: @override=extend | replace attribute
Add an @override attribute. The value of the @override attribute
is either "extend" or "replace". If the value of the @override
attribute is "extend", the overriding element extends the target
element for purposes of the class use. If the value fo the
@override attribute is "replace", the overriding element replaces
the target element for purposes of the class use. In both cases,
the target element is the child of the target class whose name
matches the name of the overriding element.
It is an error that is signaled at compile time and/or runtime if
one of these conditions holds:
- The @override attribute has a value other than "extend" or
"replace".
- The target element does not exist.
Case 1: extending a class child
Subclass:
<class name="myclass" extends="baseclass">
<view name="top" override="extend" height="40" bgcolor="blue">
Instance:
<baseclass>
<view name="top" override="extend" height="40" bgcolor="blue">
Case 2: replacing a class child
Subclass:
<class name="myclass" extends="baseclass">
<mytop name="top" override="replace" height="40" bgcolor="blue">
Instance:
<baseclass>
<mytop name="top" override="replace" height="40" bgcolor="blue">
Notes
1. The extension feature bears a resemblance similar to advice in
Lisp, method combinators in CL, and point cuts in AOP. The
difference is that those are all methods of intercepting control
flow; whereas what's necessary here is a way to intercept
structure creation. This is because the OpenLaszlo platform makes
extensive use of declarative programming to define initial
structure; state that in one of these other languages might be
created through the execution of code, is often in OpenLaszlo
created through the accumulation of structure. Using classes in
OpenLaszlo requires the same tools to modify structure that
classes in other languages require to modify control flow.
2. The replace features is also similar to overriding a method in
an OO language (and for the same reasons as in (1)). In fact,
that was why I rejected a simpler version of the proposal, where a
class use element would replace a class definition element, even
with no additional annotation. The lack of a syntactic
distinction between adding a method and replacing a method has
proved problematic in programming languages, and the newer
languages (C#, the JavaScript 2.0 proposal) use an override
keyword to signal intensional overriding. It looked likely that
making replacement look the same as additional would be a problem
with this feature too.
3. It would be possible to extend this to reach deeper inside
class definitions --- child of children, say --- by extending the
syntax of the @extends and @overrides, or @name, element to allow
dotted paths. This would be an argument for the first proposal,
since the value of @name elsewhere in the system has the lexical
syntax of a JavaScript identifier.
4. It would also be possible to extend this to reach deeper by
nesting the annotations:
<class name="myclass" extends="baseclass">
<view extends="top">
<view extends="upperleftcorner" opacity="0.5">
or:
<class name="myclass" extends="baseclass">
<view name="top" override="extends">
<view name="upperleftcorner" override="extends" opacity="0.5">
5. This proposal relates to the @placement and @defaultplacement
attributes. I don't think it's necessary to combine them, but
they should all be documented together.
6. The proposal needs to be define what happens when two override
elements within a single class use share a target element.
_______________________________________________
Laszlo-user mailing list
[email protected]
http://www.openlaszlo.org/mailman/listinfo/laszlo-user