Never have I written any graphical program; the closest I’ve come is my Meta-Machine Code tool whose interface is implemented with ECMA-48 control codes across a base and high library. Complicated and limited, it was still less complicated than writing a graphical program, for I was able to avoid the use of gargantuan libraries which still somehow serve as the best solutions for this simple problem. This sad state of things has always disgusted me, since I comprehended it. It’s wholly unreasonable to expect even experienced programmers to write many hundreds of lines of …
Never have I written any graphical program; the closest I’ve come is my Meta-Machine Code tool whose interface is implemented with ECMA-48 control codes across a base and high library. Complicated and limited, it was still less complicated than writing a graphical program, for I was able to avoid the use of gargantuan libraries which still somehow serve as the best solutions for this simple problem. This sad state of things has always disgusted me, since I comprehended it. It’s wholly unreasonable to expect even experienced programmers to write many hundreds of lines of code for that basic matter of creating a window, and I know this state was once better even on common systems. Tools to create graphical programs very easily, using graphical tools which handled the minutiae, were slowly phased out because they made programming so simple even children could write useful programs. Certainly, I could master this meaningless minutiae, but the more I program and the more I find better uses of my time, the more I realize most knowledge related to programming to have only a deeply negative worth.
The program I review is Decker, detailed documentation available here.
Decker is inspired by Hypercard; it provides a fixed-resolution, mostly-monochromatic window through which the user may build a graphical program out of cards organized into a deck. Cards have widgets taking from the following set of primitives: buttons are obvious; fields hold text, ``rich’’ or not; sliders collect bounded values as a scrollbar would; canvases are for drawing graphics; and the grid is for tabular data. Contraptions are widgets built from these primitives which behave as any other would, for the most part. Decker’s default is placing widgets freely, but has an optional mechanism to achieve alignment with either other widgets or an invisible grid. This is how easy it should be.
Decker’s accompanying little language is named Lil, which I view as an array language, but which may be used ``normally’’ with the usual loops and the like, or declaratively by treating data structures as databases. Lil is simple, easy to understand, and well-documented. Lil has the design hallmarks of a toy language, primarily in its coercion rules and other decisions taken to avoid errors, though it’s quite a nice toy language. Lil has a not-quite bottom value, nil, which is the usual result of otherwise-erroneous executions. Particularly well-designed are its interfaces, which act as the way to act with sounds, images, widgets, the outside world in certain ways, and most anything else which fits poorly elsewhere. Interfaces act like dictionaries, refuse to enumerate their keys, and remind me of Ada’s limited types. They’re a clean, extensible way to solve the problem, and users may make contraptions with their own extended interfaces, leading me to my primary criticism of Lil: I’m used to languages like Lisp with few or no arbitrary limitations, but Lil is littered with them mostly to do with names; certain names or shapes thereof are reserved by the language in a few different ways, without the possibility of customization, although I understand why it’s so, and it’s not egregious.
I’m lightly-acquainted with John Earnest, as he’s the man who organized and ran the Octojam in which I participated for many years. While I trust John to add not malware to his code which would run on another man’s machine, trust is made to be tested, so I’ve audited the copy of the Decker repository I run, except the awk interpreter for Lil, and noticed no malice. However, that C language code has been written in the queer style common to array language users who write such: lines are quite long; code which would be split between lines isn’t; there are many complex looping macros, some used only once; comments are so sparse the few that remain seem comical; and the code is extremely terse. I’d have been unable to understand it at all had I not read the documentation in such detail beforehand.
This code, as an example, from memory by now, constructs ``Lil is’’ and ``Lil make’’ type functions:
#define lm(n,c) int li##n(lv*x){return x&&x->t==c;} lv*lm##n
lm(n ,0)(double x){intern_num;lv*r=lmv(0);r->c=1,r->nv=isfinite(x)?x:0; return r;}
lm(s ,1)(int n) {lv*r=lmv(1);r->c=n;r->sv=calloc(n+1,1); return r;}
lm(l ,2)(int n) {lv*r=lmvv(2,n); return r;}
lm(d ,3)(void) {lv*r=lmvv(3,16);r->c=0,r->kv=calloc(16,sizeof(lv*)); return r;}
lm(t ,4)(void) {lv*r=lmvv(4,16);r->c=0,r->kv=calloc(16,sizeof(lv*)); return r;}
lm(on ,5)(str n,lv*r,lv*b) {r->t=5,r->sv=n.sv,r->b=b; return r;}
lm(i ,6)(lv*(*f)(lv*,lv*,lv*),lv*n,lv*s){lv*r=lmv(6);r->f=(void*)f,r->a=n,r->b=s; return r;}
lm(blk,7)(void) {lv*r=lmvv(7,0);r->sv=calloc(32,sizeof(char)),r->ns=32,r->n=0;return r;}
lm(env,8)(lv*p) {lv*r=lmd();r->t=8;r->env=p; return r;}
lm(nat,9)(lv*(*f)(lv*,lv*),lv*c){lv*r=lmv(9);r->f=(void*)f,r->a=c; return r;}
All Lil values are stored in a record capable of holding any value, an obvious way to handle such in the C language. The code is straightforward enough in many parts, but I struggled to understand it, and can claim only to have audited the code for obvious malice; I’m uneasy at the idea of using code from another in Decker, from the possibility of a flaw in the implementation allowing malicious code to run wild. I expect to use unknown decks only after auditing their inert representation, which is simple enough, or in the WWW Decker. I nearly avoided native Decker, but the hypocrisy sickened me.
Much of that C language code comprises chains of conditional code pursuing polymorphism. The notion of implementing Lil myself at some time crosses my mind, as the code and documentation together more than suffice for such, and the result perhaps would be interesting. I’ve somehow crashed Decker, in a menu or the like twice now, but this may be some oddity of my system. I find Decker feels stable.
I contrast Decker with Uxn, favouring Decker. In any design such as these, arbitrary decisions must be made, and so many appear to believe copying computers from the 1980s to make their decisions less arbitrary, yet this only makes them inefficient; I loathe overuse of the concept ``amortization’’ in discussions about computing, but languages like Lil may amortize time spent in their interpreters by keeping most computation in a ``kernel’’ compiled elsewhere, commonly through array operations. The approach used by Uxn will never be more efficient without undue work it won’t realistically receive. Decker’s high-level approach means more can be achieved with less code, which is also always better.
I expect to extend this review once I get more practice by making something significant with Decker.
There’s an annual jam about Decker, I could’ve sworn I’d seen ``Deckember’’ somewhere, in the spirit of the Octojam, held in December. I plan to participate with a tool to create the pleasing patterns of which I’m so fond. I would’ve been displeased with any imperfection on a computer of all things, and thus put my patterns to bits by learning enough of the SVG standard to write the XML manually in GNU Emacs, and learning the CSS used to animate it in such an asinine way was another waste of time, but alas. With Decker, I’ll finally build a graphical editor for these patterns, and that knowledge of XML won’t be entirely useless, for Decker has facilities to manipulate XML I can offer in output.
Decker’s powerful enough to implement a version of my Meta-Machine Code tool, and I’ve been planning how to go about that task also. Decker’s principles include transparency of storage since all decks can be inspected with a menu option which, alongside other limitations, requires one to mull over an interface with a little less flexibility in some respects than perhaps expected, but these are minor limitations. Decker’s an ideal candidate for throwaway software, that software so easy to create it becomes disposable at the first major inconvenience, but I believe it has purposes well beyond such.
I anticipate showing off my Decker decks, using screenshots taken within Decker itself, across 2026.