Some feel that HelpingTheCompilerIsEvil (this page is intended as a ReFactoring/expansion of that one). Examples are given of different ways one helps the compiler, and it is claimed therein that languages which require lots of help from users are thereby poorly-designed.
'Note that, at most, this page is about language design. Nowhere is it argued that one should refrain from "helping the compiler" if the language doesn't do the same work itself.
But what is helping the compiler? Here is a proposed definition:
- HelpingTheCompiler consists of any specification of program details which are not strictly necessary to solve the problem at hand.
That definition doesn't hold up. Consider practices such as comments or good variables names. They aren't necessary to solve any problem, but they make the code easier to read and maintain. I'd consider them to be HelpingTheCoder?, not HelpingTheCompiler.
[Note that "compiler" here also includes interpreters, VirtualMachines, and other tools.]
In many cases, it's things that could be removed from the program without changing its correctness.
For example, the "const" keyword. If you take a correct C++ program and AvoidConstCompletely (stripping out practically every reference to "const"), you'll still have a valid C++ program that does the same thing (I'm sure I will now be provided with a counter-example - I'll head off one set of counter-examples at the pass by excluding anything that contains functions overloaded based on const-ness).
OTOH, type annotations are also examples of HelpingTheCompiler, and C++ won't let you remove those.
Using the terminology of FredBrooks; HelpingTheCompiler is the specification of "accidents" rather than the specification of "essence". An ideal language would require no accidents to be specified, as accidents add complexity and increase the possibility of error.
Are you saying that all programs in this ideal language would not specify any accidents? Or are you saying that an ideal language, such specification would always be optional?
Some programming languages require help in order to compile; others may make it optional; still others may ignore or reject any help you provide. Java won't compile if you don't provide type annotations. Languages such as CommonLisp and MlLanguage don't require them (the former uses DynamicTyping, the latter TypeInference); but you can specify type annotations if you want, and the language will often check them for you. SmalltalkLanguage has no way to specify the types of terms - you could write type assertions, I suppose, and scatter them around a Smalltalk program; but the language will not accord these any special treatment.
Examples of HelpingTheCompiler include:
- Type annotations/ManifestTyping and typecasts.
- Declarations such as "const", "final", "restrict", or "volatile" which constrain the use of some entity in the language. I disagree, const and final at least are there to help the programmer (or should be when used correctly) more than the compiler. An observant reader will note that volatile doesn't quite fit. Using volatile changes the semantics of the C and C++ programs. Also, no matter how clever the compiler is (unless it can find the device manual, read it, and look for errata), it can't tell for certain if a piece of memory-mapped hardware behaves "normally" or if it should be treated as volatile.
- Various optimization hints, such as "inline" or "register" in C/C++. The compiler is free to ignore inline in c++ and register is deprecated. An observant reader will note that inline is more than an optimization hint. It affects the linkage of a function.
- Assertions and DesignByContract, if the compiler uses them to reason about the program. Again, surely these are there as an aid to the humans involved, not the machines.
- re-ordering the functions and data types in the source code, so they are always defined before their first use. Many early languages required this so they could be compiled in a single pass. (Allowing a "forward declaration", put long before the full definition, always seems to me to be a later addition to the language, added to make mutually-recursive functions and recursive data types possible). I often see the "main" starting point near the very end of C/C++/Java/AssemblyLanguage/etc. source code file; that's a sign of a former PascalLanguage programmer. (or perhaps former ForthLanguage programmer?)
- re-ordering variable declarations inside a function so all variables are declared first, before they are used. You can take any C program (which must be written in this style), move variable declarations down to the DeclareVariablesAtFirstUse style, and compile it (with a C++ compiler), and the new program will run exactly the same (the two executables may even be byte-for-byte identical).
- unless you have a variable called new....
Reasons that
HelpingTheCompilerIsEvil, according to various
WikiZens:
- As mentioned above, it requires the user to deal with additional complexity
- It limits the adaptability and dynamic nature of programs
- It makes it harder to change/ReFactor programs; not only must the essence be changed but the accidents as well. (Changing a function from const to mutable in C++ is a well-known example; often times one such change propagates itself throughout the program - but some people claim that is a good thing, in ConstIsaVirus).
- It is PrematureOptimization
- It forces the programmer to "say things that aren't true" to get code to compile.
- For programs which are deployed in piecemeal fashion, it is for different modules to get out of sync with regard to their compiler help; resulting in rather nasty bugs.
- A SufficientlySmartCompiler ought to figure stuff out on its own - that's what computers are for. Compilers that need help are broken.
- Languages that require lots of help have a name: BondageAndDisciplineLanguages
- When the compiler trusts the hints without confirming them, bad things happen. Some hints from the programmer the compiler just has to trust. The C "restrict" keyword (which declares to the compiler that a given term is not destructively aliased; allowing additional optimizations such as register caching across function calls) is one such example; if you declare a pointer to an aliased array to be "restrict" and compiler with optimization, you likely will see strange and wonderful things occur, without nary a peep from the compiler. (In other words, you're on your own).
Reasons that HelpingTheCompilerIsGood
?:
- It forces the user to think
- In many cases, it serves as documentation for the programmer's intent. It's very nice to know in C++, for instance, that a given function will not mutate an object passed to it, or that I cannot take the square root of a Canvas.
- Despite platitudes about SufficientlySmartCompilers; there are many instances of reasoning about programs where the algorithm to infer a program's attributes are intractable or undecidable; whereas algorithms to verify attributes specified by the programmer are simple. Compare TypeInference with ManifestTyping - the former is still an area of active research (and in many cases is undecidable, which is why DynamicTyping advocates beat their chest about statically-type programs being a subset of dynamically-typed programs); verification of ManifestTyping is a simple problem with many well-known solutions. Alias analysis is another thing which is difficult for compilers to do automatically. Adding such hints (during the OptimizeLater stage) allows the compiler to run faster (it can just confirm that the fact suggested by the hint is true, rather than try to work it out from first principles), and/or allows the compiled program to run faster (take advantage of the reduced compile-time per pass to enable additional optimization passes).
- A compiler, given suitable information about how the program works, can either a) optimize it, sometimes by a significant factor, or b) verify correctness, and reject erroneous programs before they are run. Detecting errors earlier is always a good thing.
- Even if the compiler doesn't use the "help" to detect errors or optimize performance, it can be used to verify the user's intent. An attempt to modify a variable declared "final" is surely a bug. Misspelled variable names and function names become errors (usually) rather than new variables. See IntentionalRedundancyDoesNotViolateOnceAndOnlyOnce. (Isn't "detecting that the program isn't what was intended" the same as "detecting errors"?)
Very bad definition. For example, many good programmers in Haskell and Ml write the type specification even if TypeInference is good enough in those languages to allow them not to. The reason is that types are not really intended to help the compiler; when they do that, they do it merely as a side effect.
Agreed somewhat; information that has benefit to the compiler frequently has benefit to programmers as well. 'Tis discussed above. On the other hand, it can often get in the way - many languages with StaticTyping are crippled by rather weak type systems. Java is the most notorious example, though 1.5 ought to fix much of that. Type systems that force CutAndPastePolymorphism? are a key aspect of BondageAndDiscipline.
The main things types were intended for is to help the programmer and the reader think clearly rather than fuzzy. That's the whole point that ranters against static typing keep missing, therefore all this handwaving and dismissive rhetoric about HelpingTheCompiler has precious little value or insight.
In any OO language, types play a much greater role than "helping the programmer think clearly rather than fuzzy". DynamicDispatch simply wouldn't work without some notion of "type", though you could use PrototypeBasedProgramming and have it such that every object is of a type unto itself. Useful in some circumstances, though certainly not all the time. I'm not an advocate of DynamicTyping everywhere. On the other hand, the maintenance of types is frequently little more than bookkeeping; the sort of task that is better left to computers rather than humans. I know that there is an old school of thought (spanning many disciplines, well beyond computer science) that bookkeeping and dabbling-in-details is good for you. I don't subscribe to that school of thought universally (though in circumstances I'm inclined to agree). But when the goal is to produce software on a schedule and a budget, I'm for letting the computer worry about the details.
I'm all for FlexibleTyping?, or SoftTyping, myself. If you want to specify types, go ahead. If you want the computer to figure it out for you, it will happily do so.
As JFK said, or rather the opposite thereof: ask not what you can do for your compiler, ask what your compiler can do for you! I don't care as much about my compiler (javac, hence) optimizing my code, as much as I like the fact that it helps me write better code, by typechecking it. I want typechecks! - WouterLievens?
CategoryCompilers