
\documentclass[12pt]{article}
\usepackage[T2A,OT1]{fontenc}
\usepackage[default]{cantarell}
\usepackage[a4paper, top=20mm, bottom=20mm, left=20mm, right=20mm]{geometry}
\usepackage[utf8]{inputenc}
\usepackage[russian, english]{babel}
\usepackage{tabu}
\usepackage{hyperref}
\usepackage{parskip}
\usepackage{graphicx}
\usepackage{tabularx}
\usepackage[normalem]{ulem}
\usepackage{float}
\floatstyle{boxed}
\restylefloat{figure}
\usepackage{setspace}
\onehalfspacing
\author{Artyom Bologov \href{mailto:cutting-corners@aartaka.me}{(email)}}
\date{\today}
\title{Designing the Language by Cutting Corners}
\makeatletter
\def\endenv{\expandafter\end\expandafter{\@currenvir}}
\makeatother
\begin{document}
\maketitle

\includegraphics[width=\textwidth,height=\textheight,keepaspectratio]{./assets/cutting-corners.png}

So I'm designing a programming language.
The Programming Language, rather.
But a big problem with designing a PL is that... it's hard.
Or, rather, everyone thinks and wants it to be hard.

\begin{itemize}\item LR parsers, parser combinators, backtracting parsers, nondeterministic and whitespace-(in)sensitive parsers...
\item Multi-pass analyzers and optimizers...
\item Thousands of IRs and bytecodes: JVM, LLVM IR/Single-assignment form, bytecode interpreters, C transpilers...
\item Custom runtimes, dynamic libraries, hot reload, code-on-demand...
\end{itemize}

But do I need all of these, actually?
What if I took a much lazier approach and decided to cut some corners?
Let's do exactly that!

\section*{Cutting on Parsing: 5 Forms} \label{parsing}

Most of university courses on PL design start and end at parsing.
(Except for the ones using Lisps, but these are an exception—everyone else must suffer.)
Parsing is hard, especially so—for C-family languages.
Making proper parsers takes time, often a full quarter/semester.
I need to simplify that.
What if there were only 5 special syntax pieces to parse?

\begin{description}\item[\verb|def|]
(Re)define a variable. Aliases: \verb|let|, \verb|local|, \verb|var|, \verb|alias|. Because you only need one kind of variable definition—lexical binding.
\item[\verb|if|]
Conditionals with \verb|else|. \verb|else if| is just two \verb|if|/\verb|else| merged. \verb|end|-terminated.
\item[\verb|fn|]
Creating a new function (lambda). Aliases: \verb|function| and \verb|lambda|. Add it up with \verb|def| to define new (maybe \verb|local|) functions and you have named abstraction. \verb|end|-terminated too.
\item[Values and function calls]
\href{"#function-value"}{If there are args, it's a function call}. If not, it's a value.
\end{description}

The rest of the programming language bloat can be reduced to that.
Switch/case statements? Just use \verb|if|!
Loops? Named recursion and functional primitives!
\href{"#model"}{(With lambdas and fixed-point combinators, of course.)}
Pattern matching? You don't need it, as much as you don't need parametric polimorphism.
(Whatever that means.)
The list goes on.

Functions, definitions, conditions, values, and applications.
We've gone full circle to procedural programming.
(With lambdas and recursion, though.)
Because it's simpler this way.
And we're fully able to make things complicated even without classes or types.

\section*{Cutting on Function/Value: Functions Always Have Args} \label{function-value}

How do you decide when something is a function call and when something is just a value?
Most languages just decide to add parentheses, but that's too hard to parse.
What if I just said... that functions are whatever has arguments?

\begin{figure}[h!]\begin{verbatim}
function value value (function value)
value
\end{verbatim}\caption{Function call vs value return}\end{figure}

"But what if I need zero arguments function?"
You don't.
To think of it, zero argument functions are either constants in disguise.
\href{"#side-effects"}{Or side effects}.
You don't need either.
So it's safe to imply that function always have args.

\section*{Cutting on Operator Precedence: (Reverse?) Polish Notation} \label{precedence}

Here's a small warmup on your C-like language precedence knowledge.
What's the precedence in these?

\begin{figure}[h!]\begin{verbatim}
a + b * c
a * b ^ c
a & b | c
a && b || c
a = b || c
a = b, c
a == b || c
a-- -b + c, a
a = b() || c()
\end{verbatim}\caption{Precedence is hard (and these are not even the worst examples, I've been lazy with it)}\end{figure}

Why need precedence at all, though?
I can just use prefix/suffix notation and get rid of all these tricky rules.
Let's do that:

\begin{figure}[h!]\begin{verbatim}
* n (factorial (-- n))
\end{verbatim}\caption{Prefix notation to get rid of all the precedence problems}\end{figure}

See? Much better—zero ambiguity!
\href{"#nesting"}{(I'll handle the parentheses fatigue later.)}

\section*{Cutting on Side Effects: Let-bindings} \label{side-effects}

Side effects like input/output and assignments are what makes academy produce computation models.
Because these cases need too many smart hacks to even work, let alone get optimized.
So what if... there were no side effects altogether?

Yes, no assignments.
Just let-bindings:

\begin{figure}[h!]\begin{verbatim}
let a = function (x) x end
let b = function (x) a x end
b 3
\end{verbatim}\caption{Let is all you need}\end{figure}

One can go a step further and say that one is not allowed to do anything in between \verb|let|-s.
So all the action goes after the \verb|let| chain.
Which also makes the parsing easier: the thing is either a binding or a final action + return value.

Now, side effects are not only about assignments.
Side effects are also about environment interaction, memory poking, and I/O.
What do we do with these?
The short answer would be that you don't need them.
The pragmatic answer will be that you can encapsulate them in a small wrapper.
And keep everything else reliably functional.

\section*{Cutting on Nesting Levels: Threading and "Tail-heavy" Calls} \label{nesting}

Now this combination of prefix notation and parentheses looks fishy.
Like... Lisp?
Lisp is ugly, right?
I need to do something about it.

Clojure does threading macros (<code>-></code> and the like) to get rid of nesting.
Scheme has its own \verb|cut| and \verb|chain| macros.
Common Lisp has nothing, and that sucks.
What will our language have?

My invention: "Tail-heavy functions".
Always put the data to be threaded in as the last argument.
With that, I can come up with a way to compose things.
Like the hypothetical colon syntax (shamelessly stolen from Wisp:)

\begin{figure}[h!]\begin{verbatim}
* n : factorial : -- n
\end{verbatim}\caption{Colon syntax to reduce nesting}\end{figure}

See? Much less intimidating!
And requires just one more keyword to the language.
No need for complex macros, just build your functions as "tail-heavy."

\section*{Cutting on Computation Model: Lambda Calculus} \label{model}

\href{"#side-effects"}{With side effects out of the way},
let's get to the computation model.

You don't need Turing Machine, actually.
State-storing tape and movement instructions are only useful if one implements a side-effect-ful systems.
There's Lambda Calculus as the more elegant system without side effects.

The problem of loops is elegantly solved with a single function.
Infamous Y combinator (or its applicative version, Z combinator:)

\begin{figure}[h!]\begin{verbatim}
def Y = function (g)
  local inner = function (x) g x x end
  inner inner
end

def Z = function (f)
  local inner = function (x) f (fn y : x x y) end
  inner inner
end
\end{verbatim}\caption{Recursion-enabling combinators}\end{figure}

So let's just compile our language to raw lambdas!
Also relying on...

\section*{Cutting on Runtime: Using a Host Language} \label{runtime}

Host language.
Whatever you implement your PL in.
I'm implementing mine in Common Lisp, so I'm going to use as much of CL as possible.
Instrumenting CL built-in parser/reader for
\href{"#parsing"}{the simple syntax I have}.
\href{"#model"}{Compiling to Lisp-native lambdas}.
\href{"#types"}{With typed literals coming straight from Lisp}.

\section*{Cutting on Type System: JSON} \label{types}

We all need a simple type system.
Something lightweight and simple.
Something everyone knows.
Like JSON!

\begin{itemize}\item Numbers;
\item Booleans;
\item Arrays (lists and strings);
\item Structures;
\item Functions...
\end{itemize}

You can tell I have something else in mind.
What if that was simplified too?
Say,

\begin{itemize}\item only have arrays (cons lists, actually), and implement strings as arrays of chars.
\item And then have chars to be mere numbers.
\item And then only have numbers restricted to integers.
\item And then have record types just as mere sequences of values.
\item And then have \verb|false| = \verb|0| = \verb|null|, like in C!
\end{itemize}

Simple for both human and machine, yet useful.
But yeah, I've strayed away from JSON somewhat.
Because JSON is hard too: it has floats and named struct fields!

\section*{Cutting on Type Inference: Nominal+Structural Types} \label{inference}

Now that there's talk about types, does one need them in the language?
Do we need type inference really?
Not really.

But let's say we need types.
What then?
What kind of type inference would that be?

I'd say optional gradual type inference with nominal types.
Or, as we say here, "just call it a string lol."
One doesn't need type enforcement or inferences, and can safely ignore type hints.
If the programmer is writing type hints, it's their problem.
But if one needs to check types, they just use these hints as The Types.
Whatever you call it, it is.

\begin{figure}[h!]\begin{verbatim}
def int = type end
var sum = + (int false) (int nil) end

def bool = type end
var truth = or (bool 0) true end

def str = type end
var chr = nth 1 (str ['a', 'b', 'c']) end
\end{verbatim}\caption{Type hints}\end{figure}

(Notice the introduction of \verb|type|.
A sixth entity/keyword in our language, but a totally optional one.
It can be safely replaced with \verb|fn| almost everywhere you see it and nothing will change.
Type tags are then just \verb|identity| functions when not used as types.)

A more interesting problem: how to infer record types?
(Including conses/lists.)
The system I came up is too simple to be useful.
But hear me out:
Constructors and accessors!

\begin{description}\item[Constructors (and only them) create a typed complex thing]
e.g. \verb|cons 1 2| creates a \verb|cons int int| pair.
\item[Accessors are constructor arguments that return respectively typed values]
for \verb|cons|, the definition might be \verb|def cons = type (car cdr) ...| with \verb|car| and \verb|cdr| as accessors.
For \verb|cons int bool|, \verb|car| returns an \verb|int|, and \verb|cdr| returns a \verb|bool|.
\end{description}

This (structural typing) system allows building arbitrarily complex data structures.
And that—added to arbitrarily shapeable base language—is more than enough for practical programming.

\section*{Cutting on Packaging: File System} \label{packaging}

Now that we have a nice little language, we need to think about its ecosystem.
How does one distribute scripts or libraries?
Of course, as plaintext files—everything else is bloated.
But how do we build and resolve dependencies for it?
Let's use filesystem for that!

One of the packaging systems might be:

\begin{itemize}\item Have files in one directory (or introduce \verb|LANG_PATH| variable.)
\item Order these files lexicographically.
\item Glue them together.
\item And then just run them.
\end{itemize}

Our program can then just be something like:

\begin{figure}[h!]\begin{verbatim}
0500-bool.lmb
...
1500-cons.lmb
2000-int.lmb
3000-char.lmb
...
5500-string.lmb
...
7000-record.lmb
8000-io.lmb
...
your-script.lmb
\end{verbatim}\caption{Naming files with numbers keeps them sorted}\end{figure}

It's easy to package and distribute our libraries and script now.
The only challenge left is the organizational one: how do I make people follow the convention and use this language?
But that's a topic for another post.

\section*{All the Corners Are Cut: Lamber!} \label{lamber}

This journey of lazy PL design comes to an end.
I've ended up with
functional-nominally-optionally-typed-whitespace-insensitive-reverse-polish-notation-language.
\href{https://github.com/aartaka/lamber}{Let's call it Lamber}
and give it some stars and use!

P.S. I have to acknowledge inspiration taken from Lua and another less known project:
\href{https://srfi.schemers.org/srfi-119}{Wisp (SRFI-119)}.
Most of the syntax ideas come from Arne Babenhauserheide's work,
including tail-heavy colons and form-ending periods
(not featured in the post, but present in Lamber.)
So thanks to him for all these ideas!


\par\noindent\rule{\textwidth}{0.4pt}
\href{https://creativecommons.org/licenses/by/4.0}{CC-BY 4.0} 2022-2026 by Artyom Bologov (aartaka,)
\href{https://codeberg.org/aartaka/pages/commit/a91befa}{with one commit remixing Claude-generated code}.
Any and all opinions listed here are my own and not representative of my employers; future, past and present.
\end{document}
