I ran across a rant the other day from someone frustrated with Ruby on Rails. (I’d include a link, but the article has been deleted.) The main argument was this:
You cannot reason about your program, ever. There are no semantics!
I’m not going to take sides on whether he’s right; I’ve never written a line of Ruby. However, I believe I understand what he’s referring to. Ruby allows developers a great deal of flexibility. That flexibility can be a blessing when you want to use it yourself but a curse when others want to exercise it. The ability to redefine everything in sight can help you get your work done faster while confusing those who have to modify your code later.
The person quoted above complained that his colleagues had abused the flexibility of Ruby to the point that he couldn’t tell what their code did. He believes Ruby, and especially the Rails framework, encourages such abuse. A Ruby advocate might reply “Ruby is a powerful language, and with great power comes great responsibility. It’s not Ruby’s fault if your coworkers are irresponsible.” I don’t want to take sides in an argument over the virtues and vices of Ruby; I’m not qualified to comment. But there is a deeper issue that I do want to explore: the importance of being able to reason about code.
What assumptions can you make when looking at a line of code? You can only count on what the language enforces, but with some confidence you can depend on language conventions, at least as a working hypothesis. Therein lies the source of many programming language debates. To what extent are you willing to live with the constraints necessary to allow you to draw strong conclusions about what a line of code does and does not do? To what extent are you willing to trust your colleagues (and yourself) to make the intent of code plain?This is not an absolute distinction. No language can guarantee everything you’d like to assume about a chunk of code, and you’ve got to have some level of trust in order to work on a substantial program. But some languages do allow you to take more for granted than others.
Statically typed languages guarantee that if code compiles, at least functions are being passed the correct data types. Attempting to pass an apple to a function expecting an orange will give a compile-time error. Proponents of dynamic typing say that such guarantees are not so valuable. Knowing that your orange function will only receive oranges doesn’t assure you that it will do the right thing with that orange. Some argue that the ability to detect type errors at compile time is not worth the increased overhead of explicit typing, especially if you have good unit tests.
Functional programming languages offer different kinds of guarantees. These languages do not allow functions to change the contents of variables as a side effect, or at least they require you to explicitly mark functions that can have side effects. The only impact of calling a function is the value it returns. This makes it possible to make strong assumptions when reasoning about code.
I find functional programming more interesting than static versus dynamic typing. It is nice to know, for example, that I didn’t accidentally pass a string to a function expecting a number, for example. But it’s much more valuable to know that calling a function didn’t change the state of my program in an unexpected way. You can understand a functional programs by understanding each function in isolation. With conventional imperative programs, you have to worry more about the interactions of functions and the order in which they are called.
You can choose to program in a functional style using a non-functional language, say in Python, but doing so is a matter of convention and is not enforced by the language. This returns to the matter of trusting yourself and your colleagues rather than trusting the language.
I believe that in the future there will be more emphasis on language features that make it easier to reason about code. One driving factor is the rise of multi-core processors. Reasoning about multi-threaded code is very difficult and most programmers have avoid it until now, but programmers are being forced to write multi-threaded software in order to take advantage of multi-core processors. I don’t believe functional programming will ever become dominant, though I do believe it will become more common.