My recent silence hasn’t been due to a lack of progress, but rather an abundance of it. I’ve been deeply immersed in the development of Objective-S, and the rapid advancements have left me with a sense of awe and a need to pause and reflect.
To illustrate this progress, I’d like to present a distributed system, complete with a GUI, concisely expressed in a single tweet:
#!env stui
scheme:s3 ← ref:http://defiant.local:2345/ asScheme
text ← #NSTextField{ #stringValue:’’,#frame:(10@45 extent:180@24) }.
window ← #NSWindow{ #frame:(300@300 extent:200@105),#title:‘S3’, #views:#[text]}.
text → ref:s3:bucket1/msg.txt.
app runFromCLI:window.— Marcel Weiher 🇪🇺 (@mpweiher) August 9, 2022
This script generates a window featuring a text field. Any text entered into this field is directly saved to an S3 bucket. This process continues until the user closes the window, at which point the program ends.
Admittedly, this is a simplified illustration of a distributed system, especially since the code for the S3 simulator is not included.
However, despite its conciseness, the Objective-S script is not as simplistic as it might appear to those unfamiliar with the language. code golf.
Instead, it elegantly defines and connects the necessary elements:
- A storage combinator to interact with data stored in S3.
- A text field within a window, both defined as object literals.
- A connection that links the text field to a specific S3 bucket.
The elegance lies in the fact that the code’s structure directly mirrors the structure of the system itself. Let’s break down these components further.
Interacting with S3 using a Storage Combinator
The initial line of the script establishes an S3 scheme handler. This handler enables interaction with S3 buckets in a manner similar to working with local variables. To illustrate, this line of code saves the text “Hello World!” to a file named “msg.txt” within a bucket called “bucket1”:
| |
stdout println: s3:bucket1/msg.txt
```Our S3 simulator is accessible at http://defiant.local:2345/, hosted on a machine named “defiant” on the local network. This machine is discoverable via Bonjour and listens on port 2345. Thanks to Objective-S’s support for Polymorphic Identifiers (pdf), this URL itself can be used as a directly evaluable identifier within the language. However, this directness presents a challenge. In most programming languages, including Objective-S, using an identifier directly retrieves the value associated with the variable it represents. In the case of http://defiant.local:2345/, this would result in the directory listing of the S3 server’s root, returned as XML:
| |
text ← #NSTextField{ #stringValue:’’,#frame:(10@45 extent:180@24) }.
Similarly, a window containing the previously defined text field can be created: ` window ← #NSWindow{ #frame:(300@300 extent:200@105),#title:‘S3’, #views:#[text]}.
``` `While it would be ideal to define the text field directly within the window definition, a separate variable is currently necessary to facilitate the connection step, which we’ll cover next.
Establishing Connections Between Components
Having established the text field (within a window) and the storage destination, we need to connect them. Traditionally, this would involve procedures, callbacks, or external mechanisms to establish or define this connection. In Objective-S, however, we connect components directly:
| |
text → ref:file:/tmp/msg.txt.
| |
text → ref:var:message.
| |
text → stdout.
This would print the text field's content to the console every time the user presses Enter. Or, we can append to a file by connecting to the file's associated stream: ` text → ref:file:/tmp/msg.txt outputStream.
``` `This is not limited to single stream sinks; it can be extended to complex processing pipelines.
The key takeaway is that this is not a case of achieving compact code through overly specialized components and mechanisms (a common approach in low-code/no-code solutions that often falters beyond simple demos).
Instead, this represents a novel approach to component creation, interface definition, and component connection in an exceptionally straightforward way. gluing
Comparing Eval/Apply and Connect/Run Paradigms
With our system constructed by configuring and connecting components, the final step is to execute it. CLIApp is a specialized subclass of NSApplication designed to run without the typical app wrapper or Info.plist file. The stui script runner instantiates it before script execution, making the instance accessible via the app variable within the script.
This is where we transition from our connected component paradigm back to the familiar world of call/return, mirroring how Cocoa’s auto-generated main function interacts with NSApplicationMain().
The distinction between eval/apply (call/return) and connect/run is significant and warrants a dedicated discussion.
It’s important to note that call/return mechanisms are not entirely absent. They remain valuable for specific tasks, like transforming an element. However, for system construction, defining, configuring, and connecting components directly (“declaratively”) proves far superior to procedural approaches, including those using recently popular “fluent APIs” often mislabeled as declarative.
This project continues to exceed my expectations, and I’m incredibly enthusiastic about its potential.