My first talk was at the European Smalltalk User Group's (ESUG) annual conference in my old hometown of Cologne: (pdf)
This year's ESUG was was my first since Essen in 2001, and it almost seemed like a bit of a timewarp. Although more than half the talks were about Pharo, the subjects seemed mostly the same as back when: a bit of TDD, a bit of trying to deal with native threads (exactly the same issues I struggled with when I was doing the CocoaSqueak VM), a bit of 3D graphics that weren't any better than 3D graphics in other environments, but in Smalltalk.
One big topic was getting large (and very profitable) Smalltalk code-bases running on mobile devices such as iPhones. The top method was transpiling to JavaScript, another translating the VM code to JavaScript and then having that run off-the-shelf images. Objective-Smalltalk can also be put in this class, with a mix of interpretation and native compilation.
My second talk, I was at Germany's oldest Mac conference, Macoun in Frankfurt. The videos from there usually take a while, but here was a reaction:
danke @mpweiher für den super vortrag zu https://t.co/bllQH4qANs gestern auf der #macoun — wer einen blick auf die zukunft erhaschen wollte, der hätte deinen vortrag sehen sollen. pic.twitter.com/Dw4M4QHLNl
— oɯʎxou (@noxymo) October 5, 2019
"Anyone who wants a glimpse at the future should have watched @mpweiher's talk"
Aww, shucks, thanks, but I'll take it. :-)
I also had two papers accepted at SPLASH '19, one was Standard Object Out: Streaming Objects with Polymorphic Write Streams at the Dynamic Languages Symposium, the other was Storage Combinators at Onward!.
Anyway, one aspect of those talks that I didn't dwell on is that the presentations themselves were implemented in Objective-Smalltalk, in fact the definitions were Objective-Smalltalk expressions, complex object literals to be precise.
What follows is an abridged version of the ESUG presentation:
controller := #ASCPresentationViewController{
#Name : 'ESUG Demo'.
#Slides : #(
#ASCChapterSlide {
#text : 'Objective-SmallTalk'.
#subtitle : 'Marcel Weiher (@mpweiher)'
} ,
#ASCBulletSlide{
#title : 'Objective-SmallTalk'.
#bullets : #(
'Embeddable SmallTalk language (Mac, iOS, Linux, Windows)',
'Objective-C framework (peer/interop)',
'Generalizes Objects+Messages to Components+Connectors',
'Enable composition by solving Architectural Mismatch',
)
} ,
#ASCBulletSlide{
#title : 'The Gentle Tyranny of Call/Return'.
#bullets : #(
'Feymnan: we name everything just a little wrong',
'Multiparadigm: Procedural, OO and FP!',
"Guy Steele: it's no longer about completion",
"Oscar Nierstrasz: we were told we could just model the domain",
"Andrew Black: good OO students antropmorphise the objects",
)
} ,
#ProgramVsSystem {
#lightIntensities : #( 0.2 , 0.7 )
} ,
#ASCSlideWithFigure{
#delayInSeconds : 5.0.
#title : 'Objects and Messages'.
#bullets : #(
'Objective-C compatible semantics',
'Interpreted and native-compiled',
'"C" using type annotations',
'Higher Order Messaging',
'Framework-oriented development',
'Full platform integration',
)
} ,
#ASCBulletSlide{
#title : 'Pipes and Filters'.
#bullets : #(
'Polymorphic Write Streams (DLS ''19)',
'#writeObject:anObject',
'Triple Dispatch + Message chaining',
'Asynchrony-agnostic',
'Streaming / de-materialized objects',
'Serialisation, PDF/PS (Squeak), Wunderlist, MS , To Do',
'Outlook: filters generalise methods?',
)
} ,
#ASCBulletSlide{
#title : 'In-Process REST'.
#bullets : #(
'What real large-scale networks use',
'Polymorphic Identifiers',
'Stores',
'Storage Combinators',
'Used in a number of applications',
)
} ,
#ASCBulletSlide{
#title : 'Polymorphic Identifiers'.
#bullets : #(
'All identifiers are URIs',
"var:hello := 'World!",
'file:{env:HOME}/Downloads/site := http://objective.st',
'slider setValueHolder: ref:var:celsius',
)
} ,
#ASCBulletSlide{
#title : 'Storage Combinators'.
#bullets : #(
'Onward! ''19',
'Combinator exposes + consumes REST interfaces',
'Uniform interface (REST) enables pluggability',
'Narrow, semantically tight interface enables intermediaries',
'10x productivity/code improvments',
)
} ,
#ImageSlide{
#text : 'Simple Composed Store'.
#imageURL : '/Users/marcel/Documents/Writing/Dissertation/Papers/StorageCombinators/disk-cache-json-aligned.png'.
#xOffset : 2.0 .
#imageScale : 0.8
} ,
#ASCBulletSlide{
#title : 'Outlook'.
#bullets : #(
'Port Stores and Polymorphic Write Streams',
'Documentation / Sample Code',
'Improve native compiler',
'Tooling (Debugger)',
'You! (http://objective.st)',
)
} ,
#ASCChapterSlide {
#text : 'Q&A http://objective.st'.
#subtitle : 'Marcel Weiher (@mpweiher)'
} ,
)
}.
There are a number of things going on here:
- Complex object literals
- A 3D presentation framework
- Custom behavior via custom classes
- Framework-oriented programming
Complex object literals
Objective-Smalltalk has literals for arrays (really: ordered collections) and dictionaries, like many other languages now. Array literals are taken from Smalltalk, with a hash and round braces:#()
. Unlike other Smalltalks, entries are separated via commas, so
#( 1,2,3)
rather than #( 1 2 3 )
. For dictionaries, I borrowed
the curly braces from Objective-C, so #{}
.This gives us the ability to specify complex property lists directly in code. A common idiom in Mac/iOS development circles is to initialize objects from property lists, so something like the following:
presentation = [[MyPresentation alloc] initWithDictionary:aDictionary];
All complex object literals really do is add a little bit of syntactic support for this idiom, by noticing that the two respective character at the start of array and dictionay literals give us a space to put a name, a class name, between those two characters:
presentation := #MyPresentation{ ... };
This will parse the text between the curly brackets as a dictionary and then initialize
a MyPresentation
object with that dictionary using the exact -initWithDictionary:
message given above.
This may seem like a very minor convenience, and it is, but it actually makes it
possible to simply write down objects, rather than having to write code that
constructs objects. The difference is subtle but significant.
The benefit becomes more obvious once you have nested structures. A normal plist contains no specific class information, just arrays, dictionaries numbers and strings, and in the Objective-C example, that class information is provided externally, by passing the generic plist to a specific class instance.
(JSON has a similar problem, which is why I still prefer XML for object encoding.)
So either that knowledge must also be provided externally, for example by the implicit knowledge that all substructure is uniform, or custom mechanisms must be devised to encode that information inside the dictionaries or arrays. Ad hoc. Every single time.
Complex object identifiers create a common mechanism for this: each subdictionary or sub-array can be tagged with the class of the object to create, and there is a convenient and distinct syntax to do it.
A 3D presentation framework
One of the really cool wow! effects of Alan Kay's Squeak demos is always when he breaks through the expected boundaries of a presentation with slides and starts live programming and interactive sketching on the slide. The effect is verey similar to when characters break the "fourth wall", and tends to be strongest on the very jaded, who were previously dismissive of the whole presentation.Alas, a drawback is that those presentations in Squeak tend to look a bit amateurish and cartoonish, not at all polished.
Along came the Apple SceneKit Team's presentations, which were done as Cocoa/SceneKit applications. Which is totally amazing, as it allows arbitrary programmability and integration with custom code, just like Alan's demos, but with a lot more polish.
Of course, an application like that isn't reusable, the effort is pretty high and interactivity low.
I wonder what we could do about that?
First: turn the presentation application into a framework (Slides3D). Second, drive that
framework interactively with Objective-Smalltalk from my Workspace-like
"Smalltalk" application: presentation.txt.
After a bit of setup such as loading the framework (framework:Slides3D load.
)
and defining a few custom slide classes, it goes on to define the presentation using the
literal shown above and then starts the presentation by telling the presentation
controller to display itself in a window.
framework:Slides3D load.
class ProgramVsSystem : ASCSlide {
var code.
var system.
...
}.
class ImageSlide : ASCSlide {
var text.
var image.
#ASCChapterSlide {
#text : 'Q&A http://objective.st'.
#subtitle : 'Marcel Weiher (@mpweiher)'
} ,
)
}.
controller := #ASCPresentationViewController{
#Name : 'ESUG Demo'.
#Slides : #(
#ASCChapterSlide {
#text : 'Objective-SmallTalk'.
#subtitle : 'Marcel Weiher (@mpweiher)'
} ,
...
)
}.
controller view openInWindow:'Objective-SmallTalk (ESUG 2019)'.
Voilà: highly polished, programmatically driven presentations that I can edit interactively and with a somewhat convenient format. Of course, this is not a one-off for presentations: the same mechanism can be used to define other object hierarchise, including but not limited to interactive GUIs.
Framework-oriented programming
Which brings us to the method behind all this madness: the concept I call framework-oriented programming.
The concept is worth at least another article or two, but at its most basic boils down to:
for goodness sake, put the bulk of your code in frameworks, not in an application. Even if
all you are building is an application. One app that does this right is Xcode. On my
machine, the entire app bundle is close to 10GB. But the actual Xcode
binary in /Applications/Xcode.app/Contents/MacOS
? 41KB. Yes, Kilobytes.
And most of that is bookkeeping and boilerplate, it really just contains a C
main()
function, which I presume largely matches the one that Xcode
generates.
Why?
Simple: an Apple framework (i.e.: a .framework
bundle)
is at least superficially composable, but a .app
bundle is not. You can
compose frameworks into bigger frameworks, and you can take a framework and use
it in a different app. This is difficult to impossible with apps (and no, kludged-together
AppleScript concoctions don't count).
And doing it is completely trivial: after you create an app project, just create a framework
target alongside the app target, add that framework to the app and then add all code and
resources to the framework target instead of to the app target. Except for the main()
function. If you already have an app, just move the code to the framework target, making
adjustments to bundle loading code (the relevant bundle is now the framework and no longer
the app/main bundle). This is
what I did to derive Slides3D from the WWDC 2013 SceneKit App.
What I've described so fa is just code packaging. If you also organize the actual code as an object-oriented framework, you will notice that with time it will evolve into a black-box framework, with objects that are created, configured and composed. This is somewhat tedious to do in the base language (see: creating Views programmatically), so the final evolutionary step is considered a DSL (Hello, SwiftUI!). However, most of this DSL tends to be just creating, configuring and connecting objects. In other words: complex object literals.
No comments:
Post a Comment