Notes
2026/10/15
Programming languages classified along two axes: From abstract to concrete, from minimalist to maximalist. An abstract language abstracts away the messy implementation details of the hardware it's running on, whereas a concrete language exposes these details. A minimalist language provides a small number of orthogonal and extensible constructs, whereas a maximalist language provides a larger number of overlapping and fixed constructs.
Lambda calculus is an abstract-minimalist language. Scheme is concrete-minimalist. Haskell is abstract-maximalist. Most languages are concrete-maximalist.
The infix vs. trailing argument problem in in f(x) g (y h z): Is g an infix function ... g ... or the trailing argument g(...) of the function call f(x, g(...))? A simple solution is to distinguish prefix functions, infix functions, trailing arguments, and atoms via their first letter: An atom always starts with an uppercase letter A-Z (Foo, Bar, etc.), a trailing argument always starts with a lowercase letter a-z followed by an argument list (foo(x, y), bar(z), etc.) or is wrapped in [...] or {...}.
But maybe that's still too weird and trying to be too clever? Perhaps it's fine to privilege “regular” lists using [...] notation and only allow [...] and {...} as trailing arguments? And atoms such as Foo and Bar are just shorthand for "Foo" and "Bar" and “atoms” (really just interned strings) that include whitespace or start with a non A-Z letter always need to be wrapped in "..."?
Perhaps there's value in providing a gensym-like feature of the surface language which produces a string that is different from all other interned strings appearing elsewhere in the source code (even if they use the same name): _"foo" would become a string different from any other "foo" or _"foo" elsewhere in the source code. (Or maybe #"foo" as the syntax? The characters _ . : ; ! ? ' " \ should be reserved characters, perhaps # as well.)
If lists can be passed on the stack, does it make sense that fixed-arity function calls are baked into the language? Should f(x, y) instead just be sugar for calling the function f with the list [x, y]? But then (x, y) => { ... } would have to be a macro that emits the necessary get(...) calls to index the argument list. Additionally, if f(x, y) is to remain sugar but an argument could be something other than a list, what is the syntax for applying f to a non-list single argument x? f x? But that doesn't work for f foo(bar) without confusing it with an infix application. Or is f foo bar just sugar for f(foo(bar)) (without the arguments being passed as lists)?
Maybe requiring an explicit argument list is not any less elegant than lambda calculus single argument application. After all, lambda calculus still has an implicit stack through its enclosing scope, which is simply made explicit when always passing multiple arguments on the stack.
Should closure creation be an explicit process? By explicitly bundling a function with one or more of its arguments as a non-function data structure, for example as a list, with the function as the first element and the arguments as the rest of the list?