This is not a thorough comparison of the languages per se. I was recently contracted as web application consultant for a start-up company. My role was to make a recommendation of which out of PHP, Ruby or JAVA would be more suitable for web applications development. For contractual reason, the web application and its requirements will not be discussed.
When comparing PHP and Ruby against JAVA, their technology evangelists usually make the following point:
PHP and Ruby are dynamic languages compare to JAVA static type feature. So our first debate here will be about; which one is better, static or dynamic language?
A programming language is said to use static typing when type checking is performed during compile-time as opposed to run-time. Examples of languages that use static typing include Ada, C, C++, C#, Java, Fortran, ML, Pascal, and Haskell. Static typing is a limited form of program verification (see type safety): accordingly, it allows many errors to be caught early in the development cycle. Program execution may also be made more efficient (i.e. faster or taking reduced memory).
Because they evaluate type information during compilation, and therefore lack type information that is only available at run-time, static type checkers are conservative. They will reject some programs that may be well-behaved at run-time, but that cannot be statically determined to be well-typed. For example, even if an expression <complex test> always evaluates to true at run-time, a program containing the code
if <complex test> then 42 else <type error>
will be rejected as ill-typed, because a static analysis cannot determine that the else branch won’t be taken.
Some statically typed languages have a “loophole” in the programming language specification that enables programmers to write pieces of code that circumvent the default verification performed by a static type checker. For example, Java and most C-style languages have type conversion; such operations may be unsafe at runtime, in that they can cause unwanted behavior due to incorrect typing of values when the program runs.
Dynamically-typed language systems, compared to their statically-typed cousins, make fewer “compile-time” checks on the source code (but will check, for example, that the program is syntactically correct). Run-time checks can potentially be more sophisticated, since they can use dynamic information as well as any information that was present during compilation. On the other hand, runtime checks only assert that conditions hold in a particular execution of the program, and these checks are repeated for every execution of the program. Static type checkers evaluate only the type information that can be determined at compile time, but are able to verify that the checked conditions hold for all possible executions of the program, which eliminates the need to repeat type checks every time the program is executed.
Development in dynamically-typed languages is often supported by programming practices such as unit testing. Testing is a key practice in professional software development, and is particularly important in dynamically-typed languages. In practice, the testing done to ensure correct program operation can detect a much wider range of errors than static type-checking, but conversely cannot search as comprehensively for the errors that both testing and static type checking are able to detect. Testing can be incorporated into the software build cycle, in which case it can be thought of as a “compile-time” check, in that the program user will not have to manually run such tests.
Static and dynamic type checking in practice
The choice between static and dynamic typing requires trade-offs.
Static typing can find type errors reliably at compile time. This should increase the reliability of the delivered program. However, programmers disagree over how commonly type errors occur, and thus what proportion of those bugs which are written would be caught by static typing. Static typing advocates believe programs are more reliable when they have been well type-checked, while dynamic typing advocates point to distributed code that has proven reliable and to small bug databases. The value of static typing, then, presumably increases as the strength of the type system is increased. Advocates of dependently-typed languages such as Dependent ML and Epigram have suggested that almost all bugs can be considered type errors, if the types used in a program are properly declared by the programmer or correctly inferred by the compiler.
Static typing usually results in compiled code that executes more quickly. When the compiler knows the exact data types that are in use, it can produce optimized machine code. Further, compilers for statically typed languages can find assembler shortcuts more easily. Some dynamically-typed languages such as Common Lisp allow optional type declarations for optimization for this very reason. Static typing makes this pervasive. See optimization.
By contrast, dynamic typing may allow compilers to run more quickly and allow interpreters to dynamically load new code, since changes to source code in dynamically-typed languages may result in less checking to perform and less code to revisit. This too may reduce the edit-compile-test-debug cycle.
Statically-typed languages which lack type inference (such as Java and C) require that programmers declare the types they intend a method or function to use. This can serve as additional documentation for the program, which the compiler will not permit the programmer to ignore or permit to drift out of synchronization. However, a language can be statically typed without requiring type declarations (examples include Haskell, Scala and C#3.0), so this is not a necessary consequence of static typing.
Dynamic typing allows constructs that some static type checking would reject as illegal. For example, eval functions, which execute arbitrary data as code, become possible (however, the typing within that evaluated code might remain static). Furthermore, dynamic typing better accommodates transitional code and prototyping, such as allowing a placeholder data structure (mock object) to be transparently used in place of a full-fledged data structure (usually for the purposes of experimentation and testing). Recent enhancements to statically typed languages (e.g. Haskell Generalized algebraic data types) have allowed eval functions to be written in a type-safe way.
Dynamic typing typically makes metaprogramming more effective and easier to use. For example, C++
templates are typically more cumbersome to write than the equivalent Ruby or Python code. More advanced run-time constructs such as metaclasses and introspection are often more difficult to use in statically-typed languages.
Static typing expands the possibilities for programmatic refactoring. For example, in a statically typed language, analysis of the source code can reveal all callers of a method, and hence a tool can consistently rename the method throughout all of its uses. In dynamic languages, this kind of analysis is either impossible or more difficult because the reference of a name (e.g. a method name) cannot surely be determined until runtime.
It is hard to say which one of the “types” scores more than the other without considering the business context where they will be applied. Much of my development experiences are on large scale enterprise applications, mainly for the finance and telecoms industry. I understand that it is not because of languages, such as JAVA, are statically type that they will end up having fewer bugs. These are part of the reason behind frameworks such as JUnit and Test Driven Development methodologies. When developing critical applications such as online banking system, it is important to rid of minor errors during compilation time then having to write a unit test for that purpose. Unit tests should be focused on application and business logic’s functions test. An example of testing would be to make sure that a customer shall not be able to transfer funds if his accounts balance is less than the amount being transferred.
I will a give a thumb-up to JAVA against Ruby. The compiler’s check on object types saves me time on not writing my own type-check. Compiling increases application performances but avoiding checking for types during runtime but the possibilities of getting some type exceptions still exists such as downcasting errors. Overall, JAVA has matured much faster and became a standard in its own. The language specification is constantly being ameliorated and the features implemented are almost second to none compared to Ruby and PHP.
In part2, I will discuss developer productivity and application scalability (+security).