I like this proposal a lot. This issue has always bugged me about XML-declared UI systems, including one that I built a while back. I never got around to solving this problem, this looks like this will solve it quite nicely.

As the proposal is, I think this is a great step forward and is worth implementing. I have three comments for possible enhancements (at the risk of over-engineering):

Comment 1:
----------
For replacement syntax, it may be worth providing a mechanism whereby the ancestor class can specify the *type* of object that is allowed to replace a child element. This allows the parent element to manipulate the child element with certain knowledge that desired methods will exist in the child. For example:

1 <class name="Widget">
2   <method name="foo">
3     // do something
4   </method>
5 </class>

6  <class name="SpecializedWidget">
7    <method name="foo">
8      // do something else
9    </method>
10 </class>

11 <class name="Ancestor" onclick="myWidget.foo()">
12   <Widget name="myWidget"/>
13 </class>

14 <!-- valid use case -->
15 <class name="Subclass1" extends="Ancestor">
16   <SpecializedWidget replaces="myWidget"/>
17 </class>

18 <class name="Subclass2" extends="Ancestor">
19   <view replaces="myWidget"/>   <!-- uh oh... -->
20 </class>

Line 19 clearly is a problem, as the replaced object doesn't contain the method "foo" that the parent is expecting.

Comment 2:
----------
Will it be a compiler error to specify a name that already exists in the ancestor without specifying replace/extend? Is there a default? I feel it should be a compile error, because the extending class/ instance might name a child element without realizing that an element by the same name already exists in the ancestor class.

Comment 3:
----------
WRT Notes #3 and #4, I lean a little toward issue #3, because it allows an extending class/instance to override a child where such child is deeply nested within unnamed elements whose purpose/ existence is hidden from subclasses (for example, providing layout/ grouping):

<class name="Ancestor">
<view x="20" y="20" width="100" height="100"> <!-- for layout/ grouping -->
    <view name="child1"/>
    <view name="child2"/>
  </view>
</class>

It would be nice to be able to extend/instantiate the Ancestor class and extend/replace child1 and child2 without having to be intimately familiar with the encompassing view. That way, a later update to the Ancestor class can change the internal structure of the element (more/ fewer layout elements in-between the parent and the replaceable children) while keeping the class backward compatible.

Hope these comments are helpful,
  -Neil

On Dec 13, 2005, at 6:10 PM, Oliver Steele wrote:

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


_______________________________________________
Laszlo-user mailing list
[email protected]
http://www.openlaszlo.org/mailman/listinfo/laszlo-user

Reply via email to