Programming Language Pragmatics has been a great read so far. I'm still in the opening chapters, but already I've gained a better appreciation for the amount of variety that exists in programming languages out there. Such a seemingly simple thing as binding values to variable names can differ quite a bit from one language to the next.
Take Python and Ruby, which are very similar in many important ways: interpreted, dynamically typed, "object oriented". Check out these two snippets:
# Python code def test(): print x x = 1 test() # Ruby code def test() print x end x = 1 test()
Even though the code looks almost identical, you get two very different results. The Python code prints
1; the Ruby code throws the error
NameError: undefined local variable or method `i' for main:Object. The reason for this is that Python scopes are dynamic and Ruby scopes are static. Depending on your programming background, you may find one or the other very hard to get used to!
This is just a simple example, but it illustrates the kind of subtle differences that make one language "feel" right to some people but not others.
A more fundamental concept I've learned about is "programming without side-effects", which seems to come from the functional programming world. Programming without side-effects means that when you call a function, the only variables that matter are the inputs, and the only effect of the function is returning a value. In other words, given a set of inputs, you must always get the same answer--the answer can't vary over time, or based on the state of a database record, or whatever. It also means the function must not change the state of the world at all, so calling or not calling the function cannot change the behavior or result of some other function.
Why put these restrictions on functions? What's wrong with side-effects? Well, it turns out that if you can count on a function to be side-effect free, you can be less careful when using it. You can cache results without worrying about them becoming stale. You can skip executing it without having to prove the program is still correct. There is guaranteed to be no coupling between one function call and any other function calls that come before or after. For example, imagine your program contains this statement for logging:
if (DEBUG) print("DEBUG: Window handle: " + getHandle(window));
Let's say the
getHandle function is not side-effect free. On the contrary, it will create a handle for the window if one does not exist. You should be nervous about removing this logging statement--what if the next line of code implicitly assumes that the window already has a handle? But if
getHandle could be guaranteed side effect free, there is no way removing the logging call could change the behavior or your program. (Well, actually
That's not to say I'm going to go to work tomorrow and start programming without side-effects; far from it. While there are functional languages that encourage or even require your code to be completely side effect free, it's hard to imagine trying to achieve that in a real world Windows app written in C#; most objects are inherently stateful. But recognizing the value in programming this way will help me write better libraries by avoiding side-effects where possible.
The bottom line is that there is great value in studying different programming languages, even if you never adopt them as your own.