• About
  • Projects
  • Sources
  • Tools

Oliver Steele

Languages of the real and artificial

Home ::

Serving Client-Side Applications

By oliver - Posted on December 5th, 2004
Tagged:  
  • Essays
  • Software

[Update 2006: “Model N” never caught on. A few months after I wrote this, Jesse James Garrett coined the term AJAX. This is what the architecture described in this post is known as today.]

In a traditional server-side web application, the server renders a series of views which are downloaded, as HTML, to the client. A client-side web application is an application that is deployed from a server and displays data from a server, but can render a series of views on the client.

I’ve been watching server-side developers try to figure out how to serve client-side web applications for a few years now. Different developers, that is —- it doesn’t take years for any individual developer to figure it out. There’s often an initial stumble, which is caused by a mismatch between the obvious way to deploy a client-side web application, and the right way. The right way is simpler, but elusive.  read more »

  • oliver's blog
  • 8 comments

What is a Platform?

By oliver - Posted on October 22nd, 2004
Tagged:  
  • Software

What is a software platform? How is it different from a framework? I’ve been thinking about the issue of what defines a platform as I think about Laszlo (a platform), the Flash execution engine that the client portion runs on (another platform), and Java and .NET (two more).

Platform and framework are often used synonymously. The software senses of the words are new (software is new), so that’s fair —- the community can use them however it wants. But I think many people have an intuitive sense of the difference that is grounded in the literal meanings of the words, and is useful.

A platform, literally, is a raised horizontal surface. A framework is more general: it’s a structure that helps you build something. A platform can be a kind of framework, but not every framework is a platform. A consequence of this difference, both in the physical world and in software, is that you can only stand (directly) on one platform at a time —- say, MacOS, or Linux —- but you can use multiple frameworks together —- say, GTK and Python —- as long as at most one of them is a platform.

Let’s go with this sense of platform —- the sense that’s grounded in the physical world, and that distinguishes it from a framework —- and see where it takes us.

A platform has three properties: it’s raised, it’s flat, and it keeps you from touching the ground. These physically-grounded properties have software analogues:

A platform supports an object. The fact that a platform is raised means that the bottom of this object is higher than the ground. In the software sense, an application or other software artifact takes the place of a physical object. The application is on the platform: its lowest level is at a higher level of abstraction than would be possible without the platform. (A software platform provides libraries and services that would otherwise be implemented piecemeal within the application.)

The fact that a platform keeps you from touching the ground means that whatever underlying capabilities the platform doesn’t provide access to aren’t available. An advantage of this distance is that the abstraction level stays high, or at least consistent. (A platform is also flat.) A disadvantage is that the capabilities of the platform may be less than those of the system it runs on. A 100% Java program using the Java libraries can’t do everything that a C++ program using the OS APIs can, both in terms of OS services, and MMU-level programming and bit twiddling.

I’ll call the system that the platform runs on simply “the system”. The system is the software analogue of the ground. In the software domain, the system is just another platform.

Another advantage of a platform, that derives from these properties, is that you can move it without changing the object that it supports. This is a natural consequence of the fact that the platform is flat —- or, more generally, that the shape of the application matches the shape of the platform, not the ground —- and that it doesn’t let you touch the ground. It’s one reason that a platform might not give an application access to every feature that the system provides. If the platform provided access to these features by being porous —- by poviding direct access to system APIs —- then the platform wouldn’t really be support the applications that ran on it (it would be a framework instead), and these applications wouldn’t be portable to other systems that the platform ran on. If the platform provided access to these features by proxying each of them, the platform itself wouldn’t be portable to systems that didn’t provide those features, and would be that much harder to port to systems that provided them differently.

Some platform features derive from the system that the platform is implemented in. (If the native operating system has threads, a platform may provide native threads to its applications.) Some features derive from the implementation of the platform itself — its depth, or height, following the physical analogy. (Even if the system doesn’t have threads, a platform can implement them.) But even these depth features are limited by what the system provides: if the system doesn’t provide access to the local filesystem, the platform isn’t going to be able to add this.

Portability is a kind of meta-feature. It trades off against system features —- the more system features the platform exposes, the less portable it is, and vice versa. To the extent that the feature set of a platform is constrained by the system features necessary to implement them, portability trades off against all platform features.

But when we look at a platform stack —- a platform A that runs on top of other platforms (platform B, and platform C), that each runs on other systems (system B1, system B2, system C1, etc.) — there’s an interesting difference between portability, and the features that it trades off against. Most features are constrained by the intersection of the features of platforms B and C. If system B1 doesn’t provide a feature, and therefore platform B doesn’t, then platform A can’t either (without feature set variants). However, the portability of platform A is defined by the union of the features of platforms B and C. Applications written to platform A can run everywhere B runs — including system B1 — and everywhere C runs — including system C1 — even though neither B nor C applications can run on both B1 and C1.

How does this relate back to Laszlo? Laszlo is a platform implemented on top of a platform (the Flash 5 and Flash 6 execution engine, although it also executes in Flash 7). One of the features of the Flash 5 execution engine is portability —- it runs on the Mac, PC, Linux, and some handheld devices, and on MacOS and Windows it’s preinstalled. (This is why we targeted Flash 5 and now Flash 6, and not Flash 7 —- the earlier versions are the preinstalled ones.)

None of the other platforms under consideration runs as well in a browser, or as ubiquitously, as Flash does. If none of the other platforms could execute JavaScript as well, or render graphics as well, then we would be faced with the problem of giving up a feature of the Laszlo platform so that we could port it. But when the feature is portability itself, it doesn’t matter! A portable browser-integrated implementation already exists; another runtime may be less portable, or less portable along certain dimensions (Java runs on more phones, .NET is more likely to work with Longhorn), but it won’t detract from the portability of the original implementation.

  • oliver's blog
  • Add new comment

Web MVC

By oliver - Posted on August 27th, 2004
Tagged:  
  • Essays
  • Software

The Model-View-Controller (MVC) architecture is a standard architecture for interactive applications. In client-server programming, the MVC components are distributed across at least two nodes of a network. This leads to a set of choices about where to deploy each component of the architecture. One solution is the traditional server-based MVC model. Another is the Rich Internet Application (RIA) model. In a real-world application with client-side validation, these are more similar than they might seem.

Desktop MVC

In an interactive application, there is typically a domain model, code to present the model to the user, and code to act upon the model in response to the user manipulation of input devices such as the keyboard and mouse. For example, in a word processor the domain model is the document, which contains entities such as paragraphs, spans, and styles. The system presents the document to the user as (for a sighted user) glyphs rendered as pixel patterns, and interprets keystrokes and mouse actions as edit, formatting, and control commands.  read more »

  • oliver's blog
  • 3 comments

Model-View-Data and HTML

By oliver - Posted on August 15th, 2004
Tagged:  
  • Software

A few days ago I wrote about two uses of data: to model the domain, as in MVC, and to drive the presentation.

DocBook and HTML take two different approaches towards sections and section titles. The DocBook approach is a more useful model of document structure. But the HTML approach is a more useful representation of the presentation.


DocBookHTML

<section>
  <title>title</title>
  <para>body</para>
</section>

<h1>title</h1>
<p>body</p>

  • oliver's blog
  • Add new comment

Exceptions

By oliver - Posted on August 15th, 2004
Tagged:  
  • Software

There are two techniques for handling exceptional cases in a program: encode the exception in the return value, or use dynamic exceptions (throw and catch). The return value strategy uses either an out-of-band return value to denote an exception, or else a structured datatype (such as Haskell’s Either), multiple return values (as in Lisp), or tuples (as in Python); it requires that every function between the exception and the handler re-encode the exception, multiplex it with other exceptions, and return the exception to its caller. The dynamic exception technique requires language-level and runtime to provide mechanism for transferring control flow up the call chain from a throw or raise to a dynamically enclosing catch; it doesn’t place any requirements on intermediate clients.

The return value case used to be the only alternative. CLU introduced nonlocal control flow, and all mainstream languages support it now.

Joel Spolsky would like a return to the old days:

A better alternative is to have your functions return error values when things go wrong, and to deal with these explicitly, no matter how verbose it might be. It is true that what should be a simple 3 line program often blossoms to 48 lines when you put in good error checking, but that’s life, and papering it over with exceptions does not make your program more robust.

Others have written about some of the disadvantages of the return value technique: that it’s error prone; that it violates separation of concerns; that it obscures the design by hiding the nonexceptonal case, and (as Joel acknowledges) that it’s verbose.

Joel acknowledges the verbosity; what he’s claiming is that the return value technique, in return for this added cost, produces an added benefit that isn’t available with the dynamic exception strategy. This is the benefit of program correctness: that if execution paths are explicit in the source, the program is amenable to source inspection and it’s more likely that each dynamic execution path results in an program state that was anticipated when the program was constructed. (He says this is two ways, but I think it comes to the same thing.)

Joel speculates that in a language with complex types were easier to construct or multiple

What is Joel missing?

Joel is comparing both a technique and a discipline on the one hand, to just a technique (but no discipline) on the other. The technique he’s in favor of is return value exceptions, and the associated discipline is that of writing functions that demultiplex exception values at each call site, and remultiplexes exception values at each return site. Sure this is onerous (which he acknowledges), but it’s more robust than the alternative technique of using dynamic exceptions, if that technique is used without any associated discipline.

/tr>
Techniquereturn valuedynamic exceptions
Disciplinedemultiplex at call sites
multiplex at @return@ sites
none

tb:
http://www.corfield.org/cgi-bin/mt-tb.cgi/154

  • oliver's blog
  • Add new comment

The Type Tax

By oliver - Posted on August 15th, 2004
Tagged:  
  • Software

Explicit type declarations (“manifest types”) have two kinds of costs. One is the extra code they add to a refactored program, relative to the same refactoring in a language with implicit types. The other is the extra code that it takes to work around the inadequacies of an inexpressive type system, such as that in C++ or Java.

(A language has implicit types if not every variable or function requires a type declaration. This can be the case if the language requires the compiler to perform type inference, such as Haskell, or if the types are checked at runtime, such as they are in JavaScript or Python.)

Refactoring

One is the extra work they add to refactoring a program, and the extra maintenance cost they add to a program which has been refactored.

In Python,
h4. Create a new variable
Contrast:

<span class="highlight">StringBuffer</span> sb = new StringBuffer();

The problem with Java and C++ is the number of times you have to write a type. It isn’t enough to say var buffer = new StringBuffer();. You have to say StringBuffer buffer = new StringBuffer();. And even if you bind another variable to that value in the very next line, you have to declare the type all over again:

<span class="highlight">StringBuffer</span> b1 = new StringBuffer();
<span class="highlight">StringBuffer</span> b2 = b1;

Creating a new function

Refactoring browsers

Type inference

One solution is to infer the type of a variable at compile time. Java and C++ already infer the types of expressions; this solution assigns each variable the type of the expression that it’s initialized from . (Doing this across multiple functions is harder than I’ve made it sound, but ML and Haskell compilers have been doing it for years.)

Higher-order Programming

The other is that, in a language with an inexpressive type system (such as C++ or Java), they prevent higher-order programming techniques. Not being able to use these programming techniques is a bit like not being able to use classes: you can work around the restrictions, but there’s an ongoing cost to your code.

Collection Classes h4. Function Types h4. One-Off Data Structures

Higher-order Types

A Clever Solution

The solution is to attach the type to the value, not the variable.

The other solution is to track the type of a variable during program execution. Java and C++[1] already do this for classes.

1 C++ runtimes only track the types of instances of classes with virtual members, or when RTI (necessary for dynamic_cast) is enabled.

A problem with static typing in a language such as C++ or Java is that there are types you just can’t name: generic types, functional types, and tuples. (sequences whose elements are differently typed from each other, but the same from instance to instance). The reason you can’t name these types is that the type system is just two weak: without type constructors, each new type requires a class.

This last is a problem with manifest types, not with static types. To solve it, you need type inference. In Dylan and other languages that are strongly but dynamically typed, you can use the runtime for a poor man’s type inference, because it’s computing new typed values, so you’re checking types as the values they’re attached to used. This also allows you to get by with a less expressive type system, since you don’t have to infer the most general type of each expression. In Haskell you’ve got a more expressive type system, and the compiler does more compile-time analysis in order to compute the types.

  • oliver's blog
  • Add new comment

Java discourages refactoring

By oliver - Posted on September 21st, 2003
Tagged:  
  • Software

Java’s Refactoring Tax

It’s harder to refactor a program in Java than it is in a “lightweight language” such as Lisp, JavaScript, Python, or Smalltalk. The reason is that Java’s feature set causes some common refactorings to end up more verbose, instead of less: this discourages refactoring (because it doesn’t buy you as much), and increases the maintenance cost of a refactored program.

Nested (inner) functions and inferred types make refactoring easier. Checked exceptions, manifest types, and the final restriction on the visibility of variables to inner classes make it harder.

These problems certainly don’t make refactoring impossible. Plenty of refactoring tools are available for Java. They just raise the bar on how bad a code smell has to get before it’s worth the cost of refactoring. This leads to programs that are more verbose, and have a higher degree of redundancy, in Java than they would in more easily refactorable languages.

An Example

Here’s an example that I ran into last week. This is in the implementation of an XML document processor. The processor contains two checks for the deprecated attribute names “init” and “constraint”. The actions triggered by these attributes are similar, but not identical. Here’s the original code:

void compile(Element element) {
  ...
  if (printDeprecationWarnings && constraint != null) {
    String message = "..."; // string that mentions the name "init" and "immediately"
    if (!element.getParent().getName().equals("class")) {
      message += "..."; // string that mentions the name "immediately"
      env.addWarning(message, element);
    }
    value = constraint;
    when = IMMEDIATELY;
  }
  if (printDeprecationWarnings && init != null) {
    String message = "..."; // string that mentions the name "constraint" and "always"
    // etc.
     value = init;
    when = ALWAYS;
  }
}

This is code with a “bad smell": it’s a case of Duplicate Code.

Dry Run: Nested Functions

In a language with nested function definitions, I might factor the two if clauses into a nested function parameterized over the expressions that vary between them:

==

void compile(Element element) {
  ...
  void adjustDeprecatedAttribute(attrValue, oldname, newname, evalTime
    if (printDeprecatedWarnings && attrValue != null) {
      ...
      value = attrValue;
      when = evalTime;
    }
  }
  adjustDeprecatedAttribute(init, “init”, “immediately”, IMMEDIATELY);
  adjustDeprecatedAttribute(constraint, “constraint”, “always”, ALWAYS);
}

This refactoring comes with two bits of overhead. (1) Function and parameter names are necessary to implement the plumbing — to match up the single function definition against its two invocations. This is inherent in the use of functions for abstraction — and therefore, for refactoring — in the first place. (2) Punctuation distinguishes the new form as a function definition. This is the overhead, beyond the identifier names, of adding a new function definition.

This first draft works in a language with implicit types. (An implicitly typed language doesn’t require type declarations on its variables. The compiler may compute the types at compile-time like Haskell does, or the runtime may verify that the types of values match the operations that are applied to them like Python, JavaScript, and Smalltalk do.) Each new function definition adds a bit more overhead, in an explicitly typed language such as Java or C++:

==

void compile(Element element) {
  ...
  void adjustDeprecatedAttribute(String attrValue, String oldname, String newname, EvalTime evalTime) {
    if (printDeprecatedWarnings && value) {
      ...
    }
  }
  adjustDeprecatedAttribute(init, “init”, “immediately”, IMMEDIATELY);
  adjustDeprecatedAttribute(constraint, “constraint”, “always”, ALWAYS);
}

The explicit types make the new function a bit more verbose, and it’s verbosity with an price. The type of init is now declared twice, once in the type declaration for the variable (which I haven’t shown here), and once in the type declaration of the function that is applied to it. This duplication comes with an ongoing maintenance cost. If I change init from String to Object or AttributeValue, there’s now an additional type declaration that I have to track down and change. These changes aren’t relevant to the contract for adjustDeprecatedAttribute, which just cares that attrValue can be distinguished from null and that has a string representation.

This isn’t a big deal, but it pushes up the maintenance cost of the program marginally, to the point where I might want to wait until a block of duplicated code is two or three lines to refactor instead of one or two. It does this by counterbalancing the original motivation for refactoring the code: to eliminate duplication. In Java, the best I can do is substitute one kind of duplication for another.

Beyond Nested Functions

But wait, there’s more.

Java doesn’t have nested functions.

There’s three tacks I could try at this point. (1) I could make the new function a member function of the class — a sibling of compile, which calls it. (2) I could create a new class to hold the new function. Or (3) I could declare an interface and use it, together with an anonymous nested function, to fake a nested class.

Making a New Member

Making a New Class

Faking a Nested Function

More Radical Solutions

In Conclusion

———————————————————————————————-

In Java, I have these choices:

  1. Use conventional refactoring to create a new method: add a new private method checkDeprecatedAttribute to the class (or to another class such as that of env).
  2. Use an anonymous inner class to host a nested function.
  3. Create a non-inner class with a single method.
  4. Use intermediate variables. Change the two clauses to set parameters that are used downstream from both of them to create the error messages.
  5. Make the code data-driven.
  6. Leave the code as is.

Each of these choices has problems. I ended up choosing the last of them — the one with the code smell — as the most maintainable alternatives. In Java, it’s the best of a bad lot.

Refactoring

void checkDeprecatedAttribute(Element element, String value, String oldname, String newname) {
    if (printDeprecatedWarnings && value) {
      ...
    }
  }

void compile(Element element) { ... checkDeprecatedAttribute(element, init, “init”, “immediately”); ... checkDeprecatedAttribute(element, constraint, “constraint”, “always”);
}

Refactoring increases the complexity of the class by adding to its list of methods. The private keyword can indicate that checkDeprecatedAttribute isn’t part of the class’s public API, but there’s no way to indicate that this method is private to compile: that it’s used only by the compile method, and can (and should) be ignored by anyone reading or working on another part of the class implementation.

Refactoring also introduces a long-distance dependency between the call sites within compile and the definition of checkDeprecatedAttribute. Change a type in compile, and the change has to be propogated to checkDeprecatedAttribute. This is because refactoring increases the number of type declarations and disperses them; this is a dependency that isn’t present in the unfactored version. (It’s one of the penalties of manifest types: the type declarations metastasize on refactoring.) It’s compounded by the fact that there’s no way to keep the method definition textually close to (or better yet, inside of the function that contains) the method invocation.

It’s as though the only way to factor the common subexpression out of this:

function float f(float x) {
  return x*x + sqrt(x*x);
}

were to introduce a new class member:

private float xsqr;
function float f(float x) {
  xsqr = x * x;
  return xsqr + sqrt(x*x);
}

Imagine how that would discourage the use of temporary variables!

Refactoring tools help create the long-distance dependency. They don’t help maintain it, and they don’t make the resulting program more readable.

Faking Nested Functions I

interface checker
void compile(Element element) {

}

Faking Nested Functions II

Intermediate Variables

Data-Driven Architecture

The Status Quo

  • oliver's blog
  • Add new comment

Two Development Styles

By oliver - Posted on September 1st, 2003
Tagged:  
  • Software

The static people talk about rigorously enforced interfaces, correctness proofs, contracts, etc. The dynamic people talk about rigorously enforced testing and say that types only catch a small portion of possible errors. The static people retort that they don’t trust tests to cover everything or not have bugs and why write tests for stuff the compiler should test for you, so you shouldn’t rely on only tests, and besides static types don’t catch a small portion, but a large portion of errors. The dynamic people say no program or test is perfect and static typing is not worth the cost in language complexity and design difficulty for the gain in eliminating a few tests that would have been easy to write anyway, since static types catch a small portion of errors, not a large portion. The static people say static types don’t add that much language complexity, and it’s not design “difficulty” but an essential part of the process, and they catch a large portion, not a small portion. The dynamic people say they add enormous complexity, and they catch a small portion, and point out that the static people have bad breath. The static people assert that the dynamic people must be too stupid to cope with a real language and rigorous requirements, and are ugly besides.

This is when both sides start throwing rocks.Quinn Dunkan, via “Simon Brunning”:http://www.brunningonline.net/simon/blog/archives/000895.html#000895

Situations, forces

Resource-Limited versus Resource-Free
Development versus Maintenance

Debugging versus Evolution

Debugger-Oriented Programming
Example-Driven Development (add link —- manageability?)
http://www.testing.com/cgi-bin/blog/2003/08/27#agile-testing-project-3

  • oliver's blog
  • Add new comment

Test versus Type

By oliver - Posted on August 20th, 2003
Tagged:  
  • Software

A lightweight language such as Python or JavaScript fits a lot of program design into a small amount of source text. A heavyweight language such as C++ or Java uses more tokens to express the same design.  read more »

  • oliver's blog
  • 22 comments

DDP in LZX

By oliver - Posted on August 18th, 2003
Tagged:  
  • Software

LZX is an XML tag set for describing Rich Internet Applications. One of the features of LZX data binding

Any view in LZX can have a datapath attribute. The value of the datapath attribute is an XML path description in the XPath syntax, optionally preceded by the name of a dataset. The path selects elements from a data set, which the attributes of the view can be bound to. If the path matches multiple elements, multiple copies of the view are created.  read more »

  • oliver's blog
  • Add new comment
12next ›last »

Recent

  • Smiley Socket
  • Commit Policies
  • My Git Workflow
  • Pneumococoa
  • My No TV
  • The Biofuel Economy
  • Ambimation
  • jQuery Profile Plugin
  • The Shadow of a Legacy
  • Minimizing Code Paths in Asychronous Code
more

Categories

  • Amusements (10)
  • Essays (20)
  • Family (8)
  • General (9)
  • Health (1)
  • Illustrations (10)
  • Inventions (2)
  • JavaScript (24)
  • Libraries (21)
  • Math Education (9)
  • OpenLaszlo (31)
  • Programming (7)
  • Programming Languages (6)
  • Projects (23)
  • Python (3)
  • Ruby (7)
  • Software (3)
  • Open Source (4)
  • XML (6)
  • Software Development (8)
  • Systems Thinking (6)
  • Technology (7)
  • Tips (5)
  • Visualizations (13)
  • Words (8)

Navigation

  • Recent posts
  • Tools

Syndicate

Syndicate content

About

Oliver Steele lives in Western Massachusetts and commutes to downtown LA, where he is bringing an operating system from handwaving to reality. He was the architect of OpenLaszlo, the author of PyWordNet and other open source projects. His interests include programming languages, knowledge representation, information visualization, and math education. [more]

Tools

  • reAnimator
  • reWork

Amusements

  • Aargh
  • foldr
  • WideURL.com

Popular

  • Functional JavaScript
  • JavaScript Memoization
  • Overloading Semicolon
  • reAnimator
  • The IDE Divide
  • Visualizing Basic Algebra
Copyright 1995-2008 by Oliver Steele. All rights reserved.