First is a notion of what the goal for a great programming language is, that is, a great, not simply a good, programming language. A great programming language knows that the programmer will want to express things whatever way they want. Ok, that's nice. So, what? Well, we must discuss some things.
So, what's an exercise in turing equivalence? It is simply when we implement the facilities of one language's features in another language. For example, if we were to implement immutable data structures in C++, or if we were to implement formal function parameters in Forth. Such exercises in turing equivalence are considered exploitable, i.e. they provide useful, leverageable functionality in programming--this, as opposed to not generally useful functionality. So to review, there are many useful language features, and a great programming language will let us express code the way we want i.e. using whatever language features we would want. Therefore, the goal follows:
The goal: to optimally facillitate exploitable exercises in turing equivalence.
Let's consider a lesson from practica. Some people recognize bad code design, and in their naivete, they want to somehow force the programmer to use good code design, but this is impossible. Bad programmers will write bad code in any language. And how are they going to learn to write good code if they're not allowed to learn by shooting themselves in the foot?
I hear all the time about people who want to put arbitrary limits on code structure! This will never work! I hear about limiting the number of methods in a class, or disallowing global variables, or other such unnatural, arbitrary nonsense. The truth is, the programmer must learn how to design good code, and if they're a competent programmer, then whatever design choice they make is competent, and we have no business getting in their way! So, what is the lesson learned?
Don't design a PL for bad programmers.
A language designed for bad programmers is a burden on competent programmers. Such a language will create a vicious cycle, always attracting bad programmers to the language. The perfect programmer knows what they want. A good programmer can figure out what they want. We should always assume a competent (perfect or good) programmer, because programmers are able to improve themselves to become at least good!
After establishing lesson 2, that we can focus on competent programmers, we soon realize the third lesson:
A great programming language simply stays out of the competent programmer's way.
So now the question becomes: how do we stay out of the programmer's way? There are only 2 ways to stay out of the programmer's way:
Method #1: By already providing the programmer's desired language features.
This removes implementation detours out of the programmer's way. This would be nice, but it will never realistically always happen for all programmers, for all their uses of the language.
The second method is not such a direct approach, but we will see it's better:
Method #2: By providing the capabilities to bootstrap the features desired by the programmer.
This, while obviously not as convenient as the first option (some implementation detours are required at times), is much more scalable: the language is guaranteed to never absolutely get in the way.
The perfect programmer knows what they want. A good programmer can figure out what they want. The first method never helped anyone get exactly what they want. A great programming language will never tie the hands of a perfect or good programmer, so method 2 is the only possibility.