First, let me define what I mean by safe: the earlier a programming language catches a programming error for you, the safer it is. Haskell is extremely safe, whereas php is extremely unsafe. Some examples:
- Errors can be caught at compile time, such as mispelling of function names
- Errors can be caught at runtime, such as NullPointerExceptions, but it may be caught early or later in runtime, there's a continum
- Errors can be caught a type time if you have a method completion capable IDE, for exmple
- Errors can be caught a unit test time, but that out of the scope for this article, because I want to talk about languages and not testing practices
- Errors can be caught at QA testing time vs in production, these are also a bit out of scope
How do you measure the safety of a programming language? One can take specfic classes of errors and see how and when a language catches them. I will use the notation >> to mean safer than. A score is given for each language for each type of error corresponding to how safe they are.
- C, C++ - never catches it, they merely core dump, which does not tell you what the specific error was
- Haskell, OCaml - catches it at compile time, because you cannot have Null values unless you specifically define it in the type, and the compiler requires you to handle the null case
Array Index Out Of Bounds
- Java, Python, C#, Smalltalk - catch at runtime when you try to index the array
- C, C++ - never catches it, may or may not core dump.
- Haskell, LISP, OCaml - although you can use Arrays if you really want/need to, in which case the error is caught at runtime like Java, etc. The prefered practice of these languages is to use linked-lists instead, which will never have index out of bounds errors
- Java, C# - these are caught at compile time if you never use casting in your program. But if you do use casting, these will be caught at runtime as ClassCastExceptions at time of cast.
- C, C++ - Compile time if you don't use casting. Never caught and possible core dumps if you do.
- Haskell, OCaml - Compile time always, casting is not allowed.
Mispelled Named Parameters
- Python, Smalltalk - caught at time of method invocation
- Ruby - rubists employ a poor-man's named parameters by just passing in a Hash to methods, the languages supports syntactic sugar that make this style of method calling look like named parameters, but mispelled keys are never caught
- OCaml - caught at compile time
OCaml (2) >> Python, Smalltalk (1) >> Ruby (0)
Wrong Number of Parameters
- Python, Smalltalk, LISP - caught at method invocation
- Ruby - caught at method invocation time for the normal cases, but blocks are a special type of parameter which is always optional. So if you supply a block to a method that does not require it, ruby does not complain
- Haskell, Java, C#, C, C++ - compile time
So a language can be safer or less safe under different contexts. There obviously are other types of errors that I have not listed, but this will do for a simple comparison. I've already given scores for each language for each error type. Since some languages do not have the named parameters feature, I will average the score for each language over the error types that are relevant to it. So the Tally is:
- Haskell - scores: [2, 2, 3, 3], average: 2.5
- OCaml - scores: [2,2,3,2,3], average: 2.4
- Java, C# - scores: [1,1,2,3], average: 1.75
- LISP - scores: [1,2,1,2], average: 1.5
- Python, Smalltalk - scores: [1,1,1,1,2], average: 1.2
- C, C++ - scores: [0,0,0,3], average: 0.75
- Ruby - scores: [1,0,1,0,1], average: 0.6
It's not really fair that OCaml is scoring lower than Haskell, they should be the same, the difference is because Haskell doesn't have named parameters so the average is skewed - my scoring system is not perfect. Haskell is safer than OCaml in one aspect though. Which is that in Haskell, purely functional code is guaranteed to have no side effects and will never throw exceptions.