How to Design a Smalltalk UI Framework

It’s common for Smalltalk implementations to run on multiple platforms, and that leads to the problem of designing a framework to allow an application UI to display on all those platforms. The existing solutions (not only in Smalltalk) suffer from various shortcomings. In order to solve this problem better, Brazil from the start was based on a number of principles different from the industry examples. Here is an overview of those principles.

It’s not about widgets. A UI framework is not (just) a widget library. In that sense, Morphic is on the right track (and in the mainstream, so is WPF). Interfaces visualize information, and a good interactive visualization is typically more than a window with a few labels and list boxes plopped onto it. A UI framework needs to do much of what was traditionally expected from a structured graphics (“diagramming”) framework. Of course, buttons, list boxes and other primitive interactive elements are still necessary, but the value of a framework is primarily in composing rather than in implementing them. Which, in fact, it shouldn’t do at all.

When it’s about widgets, it’s about native widgets. Any OS provides about the same core set of primitive interactive elements. They are what the users expect to see in a “real program”, and the OS by definition does a better job implementing them than anyone could ever hope to. Emulated widgets are bound to be a poor replica of the original and are thus an exercise in futility. On the way from Smalltalk-80, one attraction of the emulated approach might have been the ability to customize, extend and roll your own, for the sake of creating more expressive UIs. But when a UI framework is also a graphics framework, expressive UIs can be built by composition rather than by widget hacking—so there really is no good reason not to use what’s already available, even if its implementation is sealed inside the OS.

Cross-platform shouldn’t mean platform-agnostic. So what about the lowest common denominator problem? It’s often assumed that using native widgets in a cross-platform environment means that only the subset of their features common across all platforms can be accessible. The mistake of this view is the assumption that it’s not cross-platform unless the user is shielded from platform differences. It doesn’t have to be that way, and shouldn’t be. Platforms are different, and a properly designed cross-platform framework should embrace and model those differences rather than try and inevitably fail creating an illusion that they do not exist. In practice, such modeling means that a framework object that represents a widget can provide access to the lowest common denominator features, while platform-specific features can be available as a “capability”—a separate object one can fetch from the widget when the capability is available (i.e. when the current platform is the one whose capabilities you ask for).

Qt is not your friend. It’s a common and understandable question—why not use an existing cross-platform framework like Qt or wxWidgets. Everything that has been said above is reason enough, and another important consideration is that a large complex hard-to-debug third party layer shielding you from the OS facilities you want to use sounds like bad news (something I dubbed as “high Space Shuttle factor” in the original implementation study). Which is also related to the last point.

If it’s not written in Smalltalk, it’s broken. There is a tendency (at least in Smalltalk-80 descendants) to do OS interaction through primitives. That is so perhaps because FFI arrived relatively late to ObjectWorks, and never was available for Squeak. Primitives are an arcane enough feature, so they are not discussed in Smalltalk style guides. If they were, the guides might have said something like this: many primitives you see in VisualWorks and Squeak should not have existed. A primitive is supposed to do what cannot be expressed in Smalltalk. If a call out to the OS or a call back from the OS cannot be expressed in Smalltalk, you need an FFI, not a VM plugin. OS interaction is not something that belongs in a primitive—and if fact, a primitive is about the worst place to do that kind of work. It means writing a large body of complex code in a low-level language with a higher probability of making a mistake, and then hiding it away in the VM where it can’t be easily debugged and fixed. Hardly a winning combination.

Comments (6) left to “How to Design a Smalltalk UI Framework”

  1. Lorenz Pretterhofer wrote:

    I thought I might like to add, this doesn’t just apply to Smalltalk either. In just about every language I’ve looked at, the UI consists of either a platform independent library, a command line (unix or not) or a consistantly alien UI (Morphic, DrScheme, Tk, …)

    This and the FFI comments are something that probably all programming languages need to improve in.

    It’s also always fun to watch the Mac guys tear a UI framework or application apart for failing to understand the Mac OS conventions and philosophy. That is, if any developer intends to write a high quality port to Linux or Mac OS (or Windows), they will always need someone well versed in the conventions of that platform, and have to change the behavior of their UI anyway.

    Even making portability simpler isn’t enough to actually port a UI or application. Its great to see a Smalltalk derivative taking the lead and making the porting process simpler rather than hiding platform differences and making it impossible.

    Thanks for the always, fun and interesting posts on Smalltalk, Newspeak and language stuff in general and I eagerly await the release of Newspeak when it’s completed.

    — Lorenz

  2. Anthony Lander wrote:

    Vassili,

    I really like the principled approach that you bring to the frameworks you design. I can’t wait to see what this one looks like “under the hood”.

    all the best,

    -Anthony

  3. Igor Stasenko wrote:

    Each time i asking , why FFI is not in a VM core, but instead going as addon – a dynamic library, the answer was that FFI is security hole. And thus, it is very dangerous thing to come by default (bundled with VM).

    I’m not sharing this view: i cannot understand, how well-tested application which using FFI to talk directly with OS could be more dangerous than same application which using primitives?
    What the difference between bugs in primitive or your code that using FFI?
    They both could lead to VM crash or memory corruption. The real difference is where you go when need to find the bug:
    – into gdb to debug VM (where you see an object pointers)
    – into debugger in your language environment (where you can explore & inspect any object you may need)

    make your choice.

  4. D wrote:

    Hope you don’t mind some questions, I’m trying to understand the Hopscotch architecture better, in particular the presenter & subject bits.

    – You mention subject = location marker + viewpoint (+ stand-in + utility methods). Might a common subject be something like this?

    Loc Marker =
    Some Starting Domain Object +
    Accessor (path) To Target Domain Object

    Viewpoint = Subset of attribute & action on Target Domain objects

    – It is normal for a presenter to contain other presenters; how about a subject containing other subjects?

    – What are the types (Subject, Presenter, Widget, etc.) of the subexpressions in your definition example:

    definition = (

    ^column: {

    majorHeading: (label: subject model name) .

    include: (Inspector on: subject model).

    etc.

    – Can you share any thoughts on how your architecture compares with some elements of the IDE patterns in http://web.cecs.pdx.edu/~black/publications/ModelExtensions.pdf ? In particular, some of the methods you might add on a subject, they would advocate adding as interface (forwarding) methods on the corresponding domain model itself.

    Thanks!

  5. Vassili Bykov wrote:

    Thanks, these are very good questions. The first and the last are hard to answer in a comment, but I hope to write a proper explanation sometime soon. To answer the other two:

    It’s not only normal, but very common for a presenter to contain other presenters. For example, a method is displayed by a MethodPresenter which can be contained by either a ClassPresenter or a SelectorPresenter (showing senders and implementors or a selector). Subjects may create other subjects–for example a class subject has factory methods that create subjects for class and instance methods. However, there usually is no explicit containment of subjects, and no need for it since containment is already tracked by presenters.

    The type of the various subexpressions is something called Fragment. Presenter is one of the subclasses of Fragment, notable for having a subject and defining its composition in terms of other fragments (that’s what the #definition method is, and why its type is Fragment). Other Fragments are either simple atomic things such as labels or links, or “generic composers” such as rows, columns or expandable/collapsible things.

  6. D wrote:

    Thank you, presenters & fragments are clearer now. I am looking forward to more posts. Some sample code of subject + presenter would be useful.