In fact I somewhat suspect that CL's tagbody is explicitly designed such that every non-trivial control structure in the language could be expanded into that.
In general this also translates to simply understanding the semantics of a language. If you can describe something in terms of another concept the listener already has an intuition for, often it makes the new thing easier to understand and learn.
Though it's easy to imagine cases where the "lower" thing is so abstract that it's hard to apprehend in the first place (e.g. "everything is just a closure!").
It would be nice if other languages allowed implementing "syntactic sugar" in an easy way. A lot of stuff that's done with reflection in C# could be replaced.
They're talking about allowing that in C#, but it's not there yet, see https://github.com/dotnet/csharplang/issues/107
For Typescript "lowering" seems a more accurate term than "compiling" in just about all cases.
There have been attempts to express compilation as a series of many more, maybe 50, intermediate steps, implemented in some LISP. I don't know if there are success stories. I think there is always a tension between modeling data structures close enough to your understanding to enable clean implementation and not modeling so many data structures that one loses track of them.
This makes implementing the next layer (compiler perhaps) simpler because it has to understand fewer syntax elements. This also makes a difference is that you could manually write the output of the lowered code yourself in the original language. Another effect is that (ideally) applying the lowering function L() to any code a second time should produce no change i.e. L(code) == L(L(code)). This is not true of compiling.
> Lowering is the transformation of higher level representations to lower level representations. For example, subscript expressions are lowered to address arithmetic operations. A compiler traditionally lowers a high level language to machine code in one or more steps.
> As a result of lowering a function its control-flow graph is generated.
> Generally each data value will start with an abstract representation, which will in turn go through a sequence of lowerings as representation choices are made. These lowerings will eventually transform both the representation and the operations upon it to fully concrete forms, down to the level of the actual bit layout of the value in memory, allowing for easy low-level code generation.
> Instead of the compiler backend lowering object operations to machine operations using hard-wired runtime- specific logic, XIR allows the runtime system to implement this logic, simultaneously simplifying and separating the backend from runtime-system details.
> After high-level analyses and optimizations are performed, HIR is lowered to low-level IR (LIR). In contrast to HIR, the LIR expands instructions into operations that are specific to the Jalapeño JVM implementation, such as object layouts or parameter-passing mechanisms of the Jalapefio JVM. For example, operations in HIR to invoke methods of an object or of a class consist of a single instruction,
matching the corresponding bytecode instructions invokevirtual/invokestatic. These single-instruction operations are lowered (i.e., converted) into multiple-instruction LIR operations that invoke the methods based on the virtual-function-table layout.
Brilliant, thanks for that explanation, I understand 'lowering' even more now!