> Needless to say that this is perfectly compatible with being a lunatics asylum.

I see... Yes, I'we been exposed to the quality of the intake of the so-called "new" universities (in the UK). Just did not think they're learning anything beyond Excel.

> I want students to write their own lexer and parser, and familiarise themselves with lexer/parser generators. This is an important part of a programmer's education.

I agree. It should follow the compilers course, once everything of importance there is properly absorbed.

> In contrast, few programmers will ever write a real code generator.

And this is where I cannot agree at all. I believe that everyone must build multiple compilers, routinely, on a daily basis.

The reason to think so is:

1) DSLs are an ultimate answer to a complexity problem. And compilation is the best way to implement them easily.

2) Compilers are often present in where you won't even expect to find anything like a compilation. Protocols, UIs, business logic, databases, whatever. And, given the property of a compiler that it can be reduced to a tiny complexity (we'll get back to it later), it is extremely important to be able to spot such patterns early and convert your architecture to a compiler.

And, of course, all such incidental compilers got absolutely nothing to do with any kind of a parsing. Their source languages are already some data structures (even graphs), almost never a plain boring stream of characters.

> The purpose of high-level languages is to abstract away from the target platform.

Impossible if your target platforms concurrency models are incompatible. You cannot fit message passing into a SPMD, and vice versa.

> Note that optimisations are by far the most expensive part of a compiler.

Not necessarily. For C++, for example, all the optimisations, even with a polyhedral vectorisation, abstract interpretation, partial specialisation and all the bells and whistles, are nothing compared to a mere template expansion. See the clang+llvm profiling data.

> Or are they genuine memory models, coming from the CPU manufacturers?

Not "coming" anywhere, unfortunately. They're all internal models. Not available even under an NDA.

> But GHC is not simple by any stretch of the imagination.

It got quite a few gotchas, to be honest. I'd definitely design it differently. And, Haskell is not the best language for this sort of things - see the "expression problem". A Scrap Your Boilerplate library is sort of fixing part of these issues, but it is not used inside GHC.

> That's because scalac's many stages cannot just live on the data feed by the immediate preceeding stage, and need to go back serveral stages sometime. Interesting.

It is exceptionally over-engineered. But, yes, there is a reason to have a backtracking in a compiler pipeline. While still having a linear pipeline (at the end of the day it is linear, but it may be branching and going back to try out certain hypothesis). I've been experimenting with this approach a lot. It does not increase complexity in any way.

> Type checking / inference in Haskell, Scala and C++ (among others) is Turing complete.

Firstly, for most of your DSLs you don't need such a type system. Secondly, all the implementations I've seen are extremely overengineered, and I've got no idea why.

In fact, any Hindley-Milner like type system is laughably trivial, and the Turing-complete part of it is very much isolated (and you don't have to code it, just use an existing library). Hindley-Milner unification is not any different from Prolog unification, so my way of implementing these type systems is to simply compile your AST into a flat list of Prolog equations (see the recent history of my posts for this method explained in depth).

> So it's very simple too.

It's not. You cannot reason about it.

> This is the point of "Total Functional Programming"

Exactly. You don't need anything beyond a simple total language for your entire compiler (or, if you're implementing some metaprogramming, abstract interpretation or a Turing-complete type system, a 99.99% of your compiler, with the rest being well-isolated and reused over and over again).

> Term rewriting is Turing complete.

You only need a subset of term rewriting, which you can express in a total language.

> In a way, JITs are simpler than AOTs in that the optimisations they run are simpler

Yet, they're interpreters, especially the tracing ones. And interpreters are inherently orders of magnitude more complex than compilers.

> By your reasoning any program written as SK combinators should be simple, because the building block are trivial.

Not really. You're combining them in a deep, horrible tree, not a linear pipeline of isolated things.

> Extraordinary claims require extraordinary evidence.

I do not see anything extraordinary in an obvious notion of corporate coding being thoroughly broken. It's a common knowledge.

> In formal reasoning it doesn't actually matter that much if information is passed explicitly by argument, or is global state, since either way, your logic has to keep track of it.

It matters. If all you have is a language as an input and another language as an output, without any global state, then every chunk of your pipeline is isolated and you can comprehend it without thinking about the rest. That's exactly why the complexity of the passes do not add up, and the total complexity is only a complexity of the worst of the passes.

> the complexity is intrinsic in the problem of compilation

I've never seen anything complex there. After over 20 years in the field.

> which is about bridging the semantic gap between two languages.

It's not a chasm, you don't have to cross it in one step. Just build a hundred of trivial passes in between, one step at a time.

> And the bigger the semantic gap, the more complicated the task.

No, complexity do not grow. Mind giving an example of the opposite?

P.S. Also, one of the nice properties of the compilers is that you can abstract out and reuse a lot of routine stuff. Register allocation? Write it once and reuse everywhere. SSA? Implement it once and reuse for any imaginable IR. Typing? See above. Constant folding, ADCE, inlining, etc? Write it once and reuse for all the possible IRs.