This site will look much better in a browser that supports web standards, but is accessible to any browser or Internet device.
While I was at YAPC::NA this year, I got a chance to catch up with a few old friends and talk with some really knowledgeable people from other parts of the Perl community. One such discussion started when Rob Kinyon told me that "exceptions are fundamentally flawed by design and should never be used." (I'm paraphrasing here. I didn't take notes on his exact words.)
I've used exceptions in multiple languages in the past decade. I've seen both good and bad usages of exceptions. I've also heard many of the arguments against exceptions over the years. (I actually touched on these years ago in the post Joel on Exceptions.) I asked if he could explain, mostly so I could hear which arguments he would use. I really expected the same old arguments. I was quite pleasantly surprised when Rob made some really interesting points in a direction I had not heard before.
If I understood him correctly, there were two major points to his argument.
These arguments were not like the normal complaints I've heard about exceptions. Rob's arguments were actually fairly well-thought-out and well-argued. During the discussion, we ended up attracting Piers Cawley into the discussion. The discussion turned pretty lively after that.
I don't think I presented my thoughts very well in that discussion. So, here goes. Let's start with the coupling argument. The simple example of Rob's argument looks something like this (in pseudo-code).
sub foo {
...
bar();
...
}
sub bar {
...
baz();
...
}
sub baz {
...
if( bad_stuff ) throw bad( "Shouldn't do this." );
...
}
Rob suggested that foo() is now coupled to baz() because it needs to be aware that a bad exception may be thrown. This would obviously be bad, because baz() is an implementation detail of bar() and foo() should only know about bar().
In fact, foo() is not coupled to baz() at all. Calling baz() has just expanded the interface of bar() to include the ability to throw a bad exception. In fact, that is no different than if the implementation of bar() were changed to throw a bad exception. This is really no different than having to change the calling signature of bar() to add a new parameter needed by baz(). The foo() function does not need to know what bar() does with the parameter (or exception), just that it is part of bar()'s interface.
What this argument does point out, however, is that the exceptions that may be thrown by a function are a hidden portion of the interface. Now, I'm sure some Java-fans will be getting smug about Java's checked exception system. Using this facility, the programmer must specify all of the exceptions that may be thrown by a method. Unfortunately, it's been my experience that system breaks down in maintenance as changes in lower-level code necessitate touching arbitrary amounts of calling code to fix up the throws clauses to deal with new exceptions.
It seems that many programmers eventually either change to unchecked exceptions or declare that all methods can throw an Exception or Throwable, so that anything is allowed. Better design skills or an architecture that converts exceptions to more generic forms as they move between layers can mitigate this issue. But, it does point out an implementation issue with exceptions. Exceptions seem to either result in a hidden interface or a lot of extra maintenance. Perhaps later implementations or better architectures will remove this issue.
The other argument that Rob made was that exceptions are basically global objects. Like globals, when you access the exception object you have little idea where in the code it was generated. Possibly the biggest problem with a real global is that it may be changed by literally any piece of code in the system. The only way to find out where the global is modified is to examine every piece of code in the system.
Rob's argument was that it is possible that you would need to examine a large amount of code to determine the context for the thrown exception, since the exception might have been thrown from a point far from where we catch it. Unlike a real global, exceptions are only created in code called by the point where we catch the exception. So, in theory, this limits the amount of code that would need to be examined. In some cases, however, this limited amount of code could still be a large section of the codebase.
Java exceptions reduce the effects of this issue to some extent by providing a stack dump in the exception. Depending on the system, that might be almost as bad as searching the code. Other systems support a method of chaining exceptions. In these kinds of systems, you catch an exception when you have more context to add and throw a new exception containing the old exception and further context information. Done correctly, this can reduce the noise of a stack dump type exception and increase the real, useful context available to deal with an exception.
One of Rob's arguments was exceptions are not actually necessary. He argued that when something goes wrong in code, there are basically two possible ways of handling the problem.
Piers made the very useful observation that exceptions give us another recovery method in between those. This allows the code to basically say I've got a problem I can't handle, someone handle this for me. If no code handles the exception, it basically degenerates to the second case. But, if higher-level code has more context and can actually handle an issue that the low-level code can not handle, an exception allows the code to turn the second case into the first.
So an exception can actually be looked as a a call for help from the lower-level code for someone else to deal with a problem.
To me, at least, Rob's arguments were interesting, but not compelling reasons to avoid exceptions. They also weren't strong enough to justify the claim that th design of exceptions are fundamentally and fatally flawed. I would also agree that certain implementations are less than ideal and that the best exception implementations only exist in the future.
A comment on the Security Now Podcast got me thinking about the concept of write-only languages. Many times over my career, I've heard people refer to a language as write only. The implication being that no one can read the code once its written. This phrase does not refer to some magical IP technology that prevents people from stealing code you've written. It reflects the belief that the language in question is so badly designed, uses such obscure syntax, or is so powerful that any program written in the language becomes almost impenetrable to anyone trying to read the code.
I would like to humbly suggest that this concept is (mostly) nonsense. The cause of the biggest complaints seems to be unfamiliarity with the language in question. Just like most of us would find ancient Egyptian or Middle English hard to read, it can be difficult to understand a programming language if you don't know the basic syntax. Even once you learn the syntax, there is a huge difference between understanding code written in the simplest form of the language and an idiomatic program written in the language.
I have programmed in Perl for a long, long time, well over a decade in fact. I have been told right to my face by fans of other languages that it is flatly impossible to write readable or maintainable code in Perl. It always comes as a shock to me, since I have written and maintained small programs and large systems in Perl for years. It would probably come as a bigger shock to the teams that worked on the Human Genome Project that used quite a bit of Perl (and were apparently able to read it). Not to mention quite a few companies that develop and run Perl code all of the time (Booking.com, the BBC, TicketMaster, and cPanel all come to mind.)
When pressed, I often find that the person making this claim has never used Perl, or possibly only wrote a handful of quick scripts. This is hardly enough experience to make a reasonable judgment.
I also have quite a bit of professional experience working in C and C++. I have been told by some that these languages are also write-only. Yet, there are millions of lines of code in each of these languages running everything from embedded systems up to large research projects, financial institutions, and video processing. Obviously someone finds this code at least somewhat readable or it would not be maintained.
The comment from Steve Gibson that kicked this off involved the Forth programming language. Now, Steve is an expert at computer security and (by all accounts) an excellent programmer. But, my experience maintaining and developing a very large Forth code-base over a period of eight years, is quite different than his opinion. Forth is very different, if you are used to most modern languages. But, that does not make it write only.
I particularly find this comment amusing from someone who writes Windows software in assembler. I believe that it is possible to write readable assembler, despite not having seen many examples. But, I'm willing to bet there are many out there who would declare assembler to be the ultimate write-only language.
When I was pursuing my degree in CS, I had the opportunity to learn and program a bit in Lisp. While the syntax is not my favorite, I found well-written Lisp to be mostly readable. I've had a similar experience with the small amount of Haskell I've done. The same goes for SmallTalk and so on.
While I'm not willing to suggest that a particular language is write-only, I can agree that some programs are effectively write-only. But that trait does not appear to be limited to a particular language. In every language I've worked in or even just read, I've seen examples of readable code and examples of garbage I hope to never have to maintain.
In many cases, the really bad examples come from people unfamiliar with the language or just inexperienced. The more readable code normally came from more seasoned programmers. The readable code also tended to become more readable as I became familiar with the idioms of the language or even the team that wrote the code.
Although it is probably not universally true, most of the people I have heard scream write only language have experience with only one or a few languages. Much like we Americans who only know one language and consider all of those foreign languages too hard, many of these one-language programmers are still focused on the One True Syntax and have not moved on to the concepts behind the syntax. (Forgive the stereotype, but it's a good analogy.)
In some cases, the opinion was not a case of single-language blinders, but just plain unfamiliarity.
Not everyone is going to like every programming language. That's a given. Some programming languages don't fit the way you think. But, claiming a language is write-only seems ridiculous. Most of the languages I don't like, I've programmed in long enough to be able to read them, even if I would prefer not to.
I also made an explicit decision after a discussion with another programmer years ago to have no opinion on any language I haven't used. Even if other people have really bad things to say about it. I don't know what it's like, so I have no opinion. I find this makes things easier if the job ever requires me to do a little maintenance on a language I don't know.
I've also found in the past that the languages that worked the most differently from the way I think were the most valuable to learn. Those languages were the ones that taught me the most fundamental aspects of programming, because I got to see familiar programming concepts in a completely new context.
Update: I realized that I should have made clear, that Gibson had made clear that his difficulty with reading Forth was due to unfamiliarity. He does have experience with other languages.
I was listening to an older episode of Software Engineering Radio where they interviewed Martin Odersky on Scala (Episode 62). In the interview, Odersky made a comment about closures being the literals of functional programming. This statement struck me as surprising. The more I thought about it, the more interesting and subtle the concept became.
In functional programming, functions are first-class values. (OOP advocates often say first-class objects, but this concept is more fundamental than that.) If a function is a value, like an integer is a value, a closure (or anonymous function) is the equivalent of a literal (like 2). This idea has been wandering around in my head for the last few days. The more I think about it, the more interesting it appears.
To get the most that you can out of an analogy, you need to apply what you know from one side of the analogy to the other to see if it gives any insight. Let's start with what we know about literals:
We all know the problems with magic literals. Leaving them scattered around the code is a guaranteed way to make unmaintainable code. If the same magic literal is used in multiple spots, the making changes is harder than it needs to be. We normally solve this problem by replacing the literal with a defined constant. If that constant is well-named, this improves the readability of the code. (If the constant is not well-named, it can actually make things worse.)
There are a few cases where we may decide to use a literal despite this fact.
The initial index for a loop is almost always 0 (for C, C++, Perl, Java, etc.). There's really no need to name this literal, because it's function is obvious and it will not change (for a given language). Just as importantly, it's hard to come up with a name that means more to us that the value of the constant itself (please don't name it ZERO).
If you are working with date manipulation code, the literals 7 and 12 are obvious enough that we might not choose to make the constants DAYS_PER_WEEK or MONTHS_PER_YEAR. This decision is not quite as obvious, but it would be relatively easy to justify not making a constant. On the other hand, most people would probably prefer MINUTES_PER_DAY to 1440 and SECONDS_PER_DAY to 86400. This shows that if the use of the literal is obvious enough, we may not need to name it. If there's any question about the purpose of the constant, we give it a name.
Another possibility would involve implementing an algorithm from a book that contained a large number of single use magic literals. You would probably keep the literals and reference the original algorithm in a comment rather than add constants that weren't in the original algorithm. In this case, being faithful to the original algorithm trumps concerns over magic literals.
The first thing that is worth considering about anonymous functions from this analogy is the possibility that giving the function a name might be wise. If the function is used in many places, giving it a name is probably good for maintenance. (Just like making a constant out of a numeric literal simplifies maintenance.) Likewise, code containing a large number of anonymous functions is likely to be hard to maintain.
Also like the magic literal issue, there may be cases where the anonymous function is obvious enough that no name is needed. I'm going to propose some examples using Perl for the syntax. This is mostly because Perl has a fairly reasonable syntax for anonymous functions. It also has three operators for manipulating lists that make use of anonymous functions: map, grep, and sort, Let's use map. (The map operator generates a new list by applying an anonymous function to all of the elements of another list.)
@newlist = map { $_*2 } @oldlist;
Once you know that $_ is the argument to the subroutine, it is obvious that this anonymous function doubles its argument. In this context, @newlist is a list where all of the elements are the doubles of the elements of @oldlist. A similarly obvious example (with better named variables) would be:
@elapsed_secs = map { $_*60 } @elapsed_minutes;
This anonymous function converts minutes to seconds by multiplying by 60. Giving the function a name would not really clarify things any. What about
@newlist = map { $_*($_*17 + 33) + 42 } @oldlist;
This example is the equivalent of a magic literal in the code. I can't imagine anyone looking at this and deciding that it is perfectly clear. Much like code containing the magic literal 17, this function should be named. Otherwise, the code is hard to read and maintain.
Another place that anonymous functions are useful is when filtering a list using grep. Like the map cases above, we can see that simple tests make good function literals.
@positive = grep { $_ > 0 } @unfiltered;
This anonymous function obviously selects positive numbers. Just like the next one selects numbers below a cutoff.
@selected = grep { $_ < $cutoff } @unfiltered.
However, just like the map, we can have examples that would benefit from naming the function.
@filtered = grep { $_ > 3 && $_ < 13 && $_%2 == 0 } @unfiltered;
It's going to be practically impossible to understand this without some help, so turning this anonymous function into a named function is probably worthwhile.
Like numeric literals, functional literals can make code harder to understand. However, giving all closures names as a knee-jerk reaction is not a valid answer. Consider naming anonymous functions when it would make the code clearer without breaking it. I'm still not sure whether anonymous functions are as bad for maintenance as numeric literals. More thought and experimentation is needed.
So far, I don't feel like I've made it through very many of the implications of anonymous functions being the functional programming equivalent of literals. I'll probably write more later, as more implications come to me.
My son has talked several times about learning to program, but he's young enough that the normal programming approaches bore him. I had tried implementations of Logo in the past, without much success.
A couple months ago, I was checking the Make magazine blog and stumbled upon an article about a Scrolling Mario game in Scratch. A little checking of the Scratch site left me convinced that this might work for a 9 year-old.
For an old programmer like myself, the graphical nature of the language is pure pain. I want to type in the expressions and code I need, instead of snapping together little blocks. The graphical nature of the thing is like programming with both hands tied behind my back. On the other hand, these same features make it much easier for the little boy. We went through the tutorial in about half an hour and he's been coding ever since. So far, I've been impressed with what he can put together.
The biggest surprise for me is what he is working on. When I started coding (much later in life than 9) I was building programs to solve problems and control things. I expected (from his other interests) that he would quickly get into games. Instead, he seems to be mostly building animated stories. Some of them quite good for the amount of time he spends on them.
If you know a young child who has some interest in programming and who doesn't have the patience for traditional languages, give Scratch a try. You may be surprised (and impressed) at the results.
Yesterday, I wrote an essay on a comment made in Jeff Atwood's Coding Horrors blog about regular expressions. While Atwood spends quite a bit of time on the two problems joke and talks passionately about regexes as a tool, he does take a swipe at Perl that seemed somewhat uncalled for.
I have seen Perl bashed by lots of people over the years. Some have never written (or read) any Perl, but still feel qualified to bash the language. I've also been told that the only reason that I could possibly like Perl is if I had not written or maintained anything serious in Perl. When I point to large applications running in a 24/7 data centers that I worked on for years, they normally change the subject. Pointing to the number of financial institutions, research groups, and large corporations that depend on Perl to function is also illuminating.
I often see regular expression bashing and Perl bashing tied together. There seems to be this weird meme running around that regexes are the only tool in the Perl toolbox. Both Atwood and Zawinski (see Atwood's post that started this line of thought) seem to take the viewpoint that Perl is nothing but regexes or that Perl somehow forces you to to do everything through regexes.
Anyone who has worked with Perl for very long has seen that the language has very strong support for regex processing. This makes sense because the original goal of the language was text processing and regexes are made for text processing. For many, Perl was their first introduction to industrial strength regexes. Maybe it's not too surprising that they decided to overuse that powerful little tool.
Perl is also a general purpose language that supports OOP as well as a procedural style. It also supports list processing and functional programming. There are modules on CPAN for controlling hardware, accessing databases, biology, and astronomy. There are also natural language processing modules, XML parsers, and web frameworks.
Perl is a powerful, flexible language that some of us use to get actual work done.
As much as some people hate Perl and find it necessary to build up their language of choice by bashing Perl, some of us find the language to be a natural tool for many jobs. I have worked professionally in half a dozen general purpose languages over my career. But for solving a problem quickly, I normally turn to Perl. And I don't just mean for quick and dirty scripts. If I need to solve something in a short period of time and be sure it will work for years, I also often use Perl.
Bjarne Stroustrup once said:
There are only two kinds of programming languages: those people always bitch about and those nobody uses.
I would say that both C++ and Perl definitely fall into the first category.
People often pick on other languages. We've all heard the complaints (that are at best half true). Almost everyone picks on Basic for being a bit of a kiddie language. Cobol is the old-style business language that rots the brain. C++ is too baroque. Python is obsessed with indentation. Lisp has too many parentheses. Staticly typed languages are too obsessive and dynamic languages are either too slow or too loose.
Although the fans of each language might insult other languages in a friendly sort of way, mention Perl and the vitriol begins to fly. I know quite a few people who really like Perl and I know at least as many who truly hate it. As Kathy Sierra pointed out a few years ago If some people don't HATE your product, it's mediocre..
Based on the level of hate that Perl seems to inspire, it's safe to say that the language is definitely not mediocre.
Programming Ruby
Dave Thomas, Chad Fowler, and Andy Hunt
Pragmatic Bookshelf, 2005.
This book has taken me a really long time to finish. It's not because of the writing. The writing is clear and readable. The material is well-presented and seems to cover the topic well. I don't see any glaring errors or anything that I didn't like about the style or presentation. Despite all of that, there appears to be something missing.
Over the years, I have taught myself over a dozen programming languages from various books. Some books were better than others. Some languages I learned because I wanted to, some I learned because I had to. With everything I've been hearing about Ruby, this sounded like a language I would want to learn. Unfortunately, this book did not give me a compelling reason to use Ruby.
Don't get me wrong, the book makes a wonderful reference. It also provides a very good tutorial. It just doesn't compel me to use the language. Different languages have different strengths, and different personalities. This book did not tell me these things about Ruby.
If you have a real need to learn Ruby or you are already driven to use the language, this book will definitely fill in what you need. If you are like me, you will probably need something else, as well. Despite that, I would recommend this book to anyone who needs to work with the Ruby language.
Recently, I've been taken with the idea of programming languages as notation. When most people look at a programming language, they see syntax and possibly an underlying paradigm. I see the same, I just find the idea that each language also provides a unique notation for expressing ideas quite appealing.
Lately, I've been thinking about how notation relates to programming paradigms. In order to explore this topic, I need to begin with some definitions. Most programmers are familiar with one or two programming paradigms: the object paradigm, the functional paradigm, the generic programming paradigm, and so on. But, if asked, most could not explain what a paradigm is. The third definition of paradigm on Dictionary.com seems to fit our needs the best. This way of viewing reality definition definitely fits the way programmers apply programming paradigms.
The term notation is not used as much in our industry, so I would suggest the second set of definitions from Dictionary.com as being closest to what I mean: a technical system of symbols used to represent special things.
The key difference between these two is that a paradigm defines how you think about problems and solutions, and a notation defines how you write about problems and solutions. This leads to the obvious (to a programmer) question, are these two concepts completely independent. It's probably safe to say that the answer is no. (The dot notation used by many languages to make method calls is not very useful in a non-object oriented language.)
The next obvious question is how separate are they. Most of us a familiar with at least a couple of different object-oriented languages, since that is pretty much the dominant programming paradigm right now. Many of the differences between these languages are not about paradigm, but about notation. We often argue about:
." vs. "->"null vs. 0The interesting thing is that in most cases, replacing one of these with the other would not change your ability to program with the language. None of them have anything to do with the paradigm. (Although I've heard people in some groups claim that a language can't be object oriented without: garbage collection, exceptions, message passing, or strong typing.) In reality, you can do object design and programming in assembly language if you choose. The problem is that the notation of assembly language is not well suited to the paradigm.
The key to a good notation is that it simplifies writing about your problems and solutions. As such, it may be worth suggesting that different notations may be useful for different problem domains and kinds of solutions. I also think that some people are more comfortable with some kinds of notation than others. Although we try not to admit it, often the choice of notation is more a point of personal preference (or habit) than of technical merit.
This is not to say that all notations are equal, some notations are more powerful or clearer than others. Even in the procedural paradigm, C is usually considered clearer than assembly. A really good example of the differences in notation is the implementation of a 2D matrix. In a language that supports matrices natively, you can add two matrices like this:
C = A + B
and multiply two matrices like this:
D = A * B
In some OO languages, you can build a matrix class and (with operator overloading) get an equivalent notation.
C = A + B
D = A * B
If the language doesn't support operator overloading, you might end up with a notation that looks like this:
C = A.add( B )
D = A.multiply( B )
Without some kind of support for user-defined types, the notation gets increasingly messy.
Now, if you do a lot of work with 2D matrices, the first notation is going to be important to you. It requires less typing, it is clear, and it's mostly what we all learned in school. If you haven't had need of 2D matrices since your last math class, the difference in notation will not be important.
This leads back to an important point. Notation determines how you write your solutions. It is possible to write object-oriented code in C. Unfortunately, the notation available in C does not support OO comfortably. The development of C++ (and Objective C and Java) made OO more popular by supplying the notation needed to support this paradigm.
The competing concepts of expressiveness and clarity are part of what defines a notation. Any time you are confronted with a new notation, the clarity point is driven home. A new notation is more difficult for you to use than one you know. On the other hand, as you become more comfortable with a notation, the expressiveness concept becomes more important. A good notation must be clear enough that new people can learn to use it, yet expressive enough to solve real problems.
Different languages make these tradeoffs in different ways. Just as importantly, different people have different tolerances for where they will accept the tradeoff. Some people prefer expressiveness in a language. They take the view that they will only be novices for a short while compared to the time they use the notation. These programmers are willing to trade a steeper learning curve (less clarity) for the power a more expressive notation gives. At the other extreme are people who prefer clarity over expressiveness. These people assume that most people will not be able to handle a very expressive notation. To this group, a simpler, clearer notation is more important than expressiveness because more people can understand it more readily.
Notice that this concept is independent of paradigm. Each notation in each paradigm makes these (and other) tradeoffs differently, based on the experience and biases of that language's creators. So, the next time you find yourself in a fight over which language is more object-oriented, you might want to consider if you are arguing paradigm or notation. It may not stop the holy war, but this viewpoint might at least confuse your adversaries.(<grin/>).
Many years ago, I was working on application software that we configured and shipped to nearly a hundred clients. Although we did the first installation on a machine that we provided, the customer had to install updates a few times a year. (This was before most people knew about the Internet.)
A few of us tried to convince the president of the company that we could make good use of a professional installation program. But, he could not see the point. He pointed out that we were already working in C, and C could do everything that the installer could do. So he concluded we could just write our own. He could not see the use of a specialized language for installation, what we would now call a Domain Specific Language (DSL).
Amusingly enough, we were already using a DSL to build all of the software. This guy was a big fan of make. We used a reasonably complex build system based around make to generate over twenty different variants of the program.
His question is actually a valid one, although at the time I didn't have the terminology to make my point. Everything I can do with a DSL, I can do in a general purpose language. In fact, I can package up the functionality for the DSL in libraries or classes and even make calling it almost as easy as using a DSL. So, why would a DSL be a advantage? I believe the answer is notation. The DSL provides no increase in functionality. But what it does do is provide a better notation for working in the domain.
Let's take make as an example. Even though almost everyone complains about the tab thing in Makefiles and the reliance on the shell, make is still in wide use. Anything you can do with make can be done with any general purpose language. It would even be possible to build libraries for common functionality needed by build tools. Yet, we still use make or other tools similar to them. Why? Despite its flaws, the make DSL is very effective for specifying dependencies and tasks needed to build one set of resources from another set of resources.
SQL is another good example of a DSL, I remember accessing databases before SQL was wide-spread. Every database API was slightly different. They all provided the same basic functionality, but there were differences. You also needed to write quite a bit of common code in every place you used a particular database's API. Then SQL came along, and things changed. No, it was not a complete language, but it did allow you to talk to the database at a higher level. In the beginning, SQL libraries were not any faster (and were often slower) than the native APIs. Nevertheless, the notation was more convenient for the kinds of operations that you need to perform on a relational database.
In many domains, a library is enough to allow you to get your work done. When the domain is understood well enough that we can really talk about it at a high level, it becomes possible to make a DSL. There seems to be a large difference in usability when a DSL is designed correctly. Even if a DSL is not perfect (like make), it may be enough better than the alternatives that still use it...even if we still reserve the right to complain.
I've been thinking lately about the differences between programming languages. Over and over again, we see religious wars over what is the best programming language. Lately, we've also seen a lot of comparisons that look at particular classes of languages.
I have always had a kind of fascination with programming languages. I have studied several languages over the years, some of them very different than others. Over time, I tended to seek out languages that were somewhat different than the ones I already know. Alan Perlis is often quoted as having said:
A language that doesn't affect the way you think about programming is not
worth knowing.
When I first ran across this quote, I felt that it explained much of my fascination with programming languages. Each language I've learned has taught different approaches to solving problems, different ways of thinking about modeling the world, or provided different tradeoffs in terms of safety and efficiency. Unlike many language zealots, I've never really felt that one languages was best for all problems. This doesn't mean that I don't have some languages I like more than others, or that I won't get into a language discussion. I just prefer to have several options instead of just one.
Lately, all of the hoopla about Domain Specific Languages (DSLs) has got me thinking about this concept some more. After quite a bit of thought, I've realized that programming languages provide something else that I hadn't really thought much about. If you ask most programmers what the important attributes of a programming language are (and strip out all of the language-bashing and such), you would probably get a list something like:
But, there is something so fundamental that we tend to overlook it. A language provides a notation for expressing solutions. By notation, I mean something a bit more than raw syntax. A notation that fits the solution and problem space, makes solving the problem almost effortless. If the notation doesn't match the problem space, the solution will be much harder.
One of the best known examples of the effect of a useful notation is the change in mathematics when Roman Numerals were replaced with decimal (positional) notation. Before a positional notation became wide-spread, arithmetic with large numbers was extremely hard. Most people who needed the ability to add, subtract or multiple large numbers used lookup tables. Very highly trained experts worked out the math to build the tables.
After a positional notation became more common, arithmetic with large number is something anyone can accomplish. In fact, we teach the techniques to multiply two numbers of any size to elementary school children. We can do this not because kids today are smarter than Roman children, but because we use a superior notation.
This applies to programming languages as well. In problems which require manipulation of matrices, the amount of syntax used in the manipulation can have a major impact on the ability to express a solution. If you need to scale a 2D matrix, which is a better notation?
for(int i=0; i < num_rows; ++i)
{
for(int j=0; j < num_cols; ++j)
{
matrix[i][j] *= 2;
}
}
matrix *= 2;
Ultimately, the implementation must do something similar to the first case to make the either solution work. But, in the second notation the unnecessary details are hidden. The first one takes a little time to read and understand before you can say for certain what it does. The second is mostly understandable at a glance. Does that make the second one a better notation? It depends. If you need to do a lot of matrix math in your programs, I think the answer would be yes. If you never do any matrix math, the notation is of no use to you, so there is no sense in cluttering your language with it.
Fundamentally, this is the reason why we need different programming languages. Some solutions are expressed better in one notation than in another. Some languages allow the programmer to adapt the language's natural notation to fit the problem.
I believe that the recent interest in DSLs results from a desire to express our solutions in a better notation. This idea has been strong in the Unix world for years where little languages are used to solve numerous problems. The Unix world is also a good place to see the importance of designing your notation carefully. It is also important to see these DSLs for what they are: a way of reducing the noise in our programs by using a high-signal notation to express a solution.
So to paraphrase Perlis:
A notation that doesn't simplify solving problems is not worth learning.
I've seen quite a bit of interest in Domain Specific Languages (DSLs) on the Internet lately. Some good examples include Martin Fowler's exploration of the subject:
* MF Bliki: DomainSpecificLanguage
* Language Workbenches: The Killer-App for Domain Specific Languages?
He does point out that this is not a new idea. He uses Unix as an example of a system that uses a large number of DSLs. The subject has gotten enough interest to reach the point where people are discussing when it is a good approach to apply (Artima Developer Spotlight Forum - How and When to Develop Domain-Specific Languages?). Others are beginning to apply the term DSL to extend their area of interest (Agile Development with Domain Specific Languages).
So, from this we can guess that interest in DSLs is on the rise. As Fowler pointed out, Unix has been a nexus for the creation of DSLs, including:
* make
* regular expressions
* awk
* sed
* yacc
* lex
* dot
and many more.
Recently, I have seen the suggestion that extending or modifying a general purpose language is a powerful way to build a useful DSL. To some extent, this is also well-trodden ground. The classic example of this approach was implemented using preprocessors to provide facilities not present in the original language, both Ratfor (for structured programming in FORTRAN) and cfront (for object oriented programming in C) used this approach.
A recent article, Creating DSLs with Ruby, discusses how the features of the Ruby language make it well suited to building DSLs without a separate preprocessing step. The Ruby language apparently supports features that supports creating simple DSL syntax that is still legal Ruby code.
This is a very powerful technique that is not very easy to do with most languages. Amusingly enough, this technique is also not very new. In fact, there was a general purpose programming language that was designed around the concept of writing a domain language: Forth. If I remember correctly, Charles Moore once described programming in Forth as writing a vocabulary for the problem domain, defining all of the words necessary to describe the answer, and then writing down the answer.
The Forth language is different than most programming languages you might have encountered because it has almost no syntax. What looks like syntax is actually a powerful technique for simple parsing, combined with the ability to execute code at compile time. This allows for extending the capabilities of the language in a very powerful way. One interesting effect of this feature is that many good Forth programmers naturally gravitate toward the DSL approach when solving problems above a certain level of complexity. We firmly believe that some problems are best served by a language, not an arcane set of configuration options.
Forth does give us an important insight into the problems with DSLs, as well. There is is a well-known joke among Forth programmers:
If you've seen one Forth...you've seen one Forth.
Unlike more traditional programming, Forth programs are built by extending the language. A new programmer trying to learn a Forth system needs to learn the new dialect including the extensions used in this environment. This is not technically much different than learning all of the libraries and utility classes used in a more traditional system, but there is a conceptual difference. In Forth (or a DSL-based system), there is no syntactic difference between the extensions and the base language. The language itself can be extended without an obvious cue in the code to say when you have changed languages. This means that a new programmer may not recognize a new piece to learn as readily as when seeing an obvious library call.
This becomes a very important tradeoff: Which is more important, ease of learning for new programmers or power for advanced users? A well-designed DSL gives the advanced user a succinct notation to use to express hie or her requirements concisely and precisely. This is the appeal of the DSL. The downside is that this represents a new set of knowledge for each programmer relating to troubleshooting and debugging. It also requires more care in the design to develop a consistent and usable notation.
As usual, the tradeoff is the key. We need to be able to decide if the benefits outweigh the disadvantages and build the system accordingly. I wish there was a magic formula that could be applied to tell how or when a DSL would improve a system. Unfortunately, I have not seen a sign of such a formula yet.
The Little Schemer
Daniel P. Friedman and Matthias Felleisen
The MIT Press, 1996.
One of my wife's friends recommended this book for learning Scheme. He's a big proponent of Scheme and has even done research into teaching Scheme to kids. He is quite knowledgeable in his field. On the other hand, I have never written a line of Scheme; although I did some coursework with LISP during my master's degree. Although I don't normally choose to work in LISP(-based) languages, I can appreciate some of their power.
I realized that this book was going to require a bit of suspension of disbelief in the preface, where I found this gem:
Most collections of data, and hence most programs, are recursive.
I agree that there are many useful collections of data that are recursive. I would even agree that many programs apply recursion. But I find the assertion that most are recursive a little strong. In fact, the only way I could see this is if the language the writers were working in treated almost everything as recursion. And, of course, this is the case.
The other real problem I had with the book is the style. The book is written as a series of questions and answers that lead you to the conclusions that they wish. Some of these question and answer sessions became quite strained; such as trying to explain how a complicated function works. In other spots, the authors asked a question that there is no way the reader could have begun to answer. The authors would then respond with something like:
You were not expected to be able to do this yet, because you are missing some of the ingredients.
I found this style very jarrng. I've learned a dozen or so languages from books (including LISP), and I've never had this much trouble understanding a computer language. The style may work for someone else, but it did nothing for me.
From the comments above, you might decide that I have nothing good to say about the book. That's actually not the case. I found the Five Rules and the Ten Commandments to be very effective ideas. The Five Rules basically define some of the most primitive operations in the language. The Ten Commandments state some important best practices.
I was also surprised at times by really sweet pieces of code and understanding that would come after some prolonged pseudo-Socratic Q&A sessions. Some of the insights and commandments are well worth the read. But, overall I found it difficult going.
Since it is considered one of the classics for learning Scheme, you should probably consider it if you are needing to learn Scheme or LISP. If all you've done is code in C or Java, it might be worth reading to introduce yourself to another way of looking at problems. But, I find it very hard to recommend this book to anyone else.
Compiler Design in C
Allen I. Holub
Prentice Hall, 1990
I decided to take a break from the relatively new books I've been reviewing and hit a real classic.
Over a decade ago, I saw Compiler Design in C when I was interested in little languages. A quick look through the book convinced me that it might be worth the price. I am glad I took the chance. This book describes the whole process of compiling from a programmer's point of view. It is light on theory and heavy on demonstration. The book gave an address where you could order the source code. (This was pre-Web.) All of the source was in the book and could be typed in if you had more time than money.
Holub does a wonderful job of explaining and demonstrating how a compiler works. He also implements alternate versions of the classic tools lex and yacc with different tradeoffs and characteristics. This contrast allows you to really begin to understand how these tools work and how much help they supply.
The coolest part for me was the Visible Parser mode. Compilers built with this mode displayed a multi-pane user interface that allowed you to watch a parse as it happened. This mode serves as an interactive debugger for understanding what your parser is doing. This quickly made me move from vaguely knowing how a parser works to really understanding the process.
Many years later, I took a basic compilers course in computer science and the theory connected quite well with what I learned from this book. Although the Dragon Book covers the theory quite well, I wouldn't consider it as fun to read. More importantly, nothing in the class I took was nearly as effective as the Visible Parser in helping me to understand the rules and conflicts that could arise.
Although this book is quite old, I would recommend it very highly for anyone who wants to understand how parsers work, in general. Even if you've read the Dragon Book cover to cover and can build FAs in your sleep, this book will probably still surprise you with some fundamentally useful information.
The book appears to be out of print, but there are still copies lurking around. If you stumble across one, grab it.
What is it about programmers that cause them to latch onto one approach and try to apply it to every problem? Maybe it's a characteristic of people in general. Each new paradigm, methodology, or even language seems to grab hold of many programmers and make them forget anything they ever knew. They seem to develop an overwhelming need to re-implement and replace all that came before the new tool.
This isn't always a bad idea. Done deliberately, this technique can help you evaluate a new methodology or language by comparing the solution of a known problem with a previous solution of that problem. Unfortunately, many of us seem to assume that the new way is better and apply the new approach despite any evidence that it does not solve the problem more effectively.
Over time, I've come to the conclusion that there is no One, True Way of Programming. This is not my own insight. I have read this and heard this from numerous people with more experience. Earlier in my career, I did not believe them. I didn't disagree, I just didn't believe. Through experience with multiple paradigms, languages, and methodologies, I've finally come to understand what those senior people were trying to say.
Multiple paradigms are different tools in my bag of tricks. Each language is another tool that I can apply to a problem. Different methodologies give me different approaches to solving a problem. But, in the end, no single set of these tools can help me solve all problems. Each combination has strengths and weaknesses. One of the most important skills we can develop is the ability to recognise the right tool for the job.
Many years ago, I saw this point made in a book. (One day I'll remember which book.) The author pointed out that very different techniques and processes are needed when you are building a skyscraper and when you are building a doghouse. The author pointed out that the techniques that would be used to architect a skyscraper are not cost-effective when building a doghouse. I think I would now go a little further and suggest that the techniques are not appropriate, and that cost is one easy measure of why they are not appropriate.
Like any natural language, most programming languages support idioms. According to the American Heritage Dictionary of the English Language, an idiom is
A speech form or an expression of a given language that is peculiar to itself grammatically or cannot be understood from the individual meanings of its elements, as in keep tabs on.
Programming idioms can, of course, be understood functionally from its individual elements. The interesting thing about most programming idioms is what they say to the programmers who will read the code later. A programming idiom can reveal many things about the author of the code. To some extent, the idioms you choose are a function of your understanding of the language, what you may have been taught in school, and the other languages you have programmed in.
I made a comment on this subject earlier in Textual Analysis and Idiomatic Perl on Perl Monks. In that article, I pointed out that idioms are cultural, just like those in natural languages. Also like in natural languages, if you only have experience in one culture and language, the idioms of another appear unnatural or wrong. When learning a language, you learn the syntax and obvious semantics. You have to live with the language for a long while to learn the idioms.
The result of this is that the idioms of a language are foreign to new speakers. As they continue to use the language, simple idioms become part of their vocabulary. At first, they will misuse or over-use these idioms. Over time, they develop an appreciation for the subtleties of the expression. Eventually, those idioms are only used when necessary. With more exposure, more idioms become assimilated into your vocabulary. Soon, you learn to speak/program the language relatively fluently. Also, like natural languages, you may always retain a bit of an accent, based on your earlier languages.
This explanation seems to explain the course I've seen many programmers, as well as myself, take with a new programming language.
The simpler the language, the quicker you get to the end of this cycle. But, a simpler language is not as expressive. So, you find yourself doing more work than should be necessary. A more expressive language takes longer to learn, but it's easier to pass on subtle shades of meaning to the maintenance programmer (possibly yourself).
This is not to say that you must use advanced idioms to be effective in a language. Sometimes, a simple expression is all that is warranted. But, complex ideas and complex problems often have subtle solutions. Subtle shades of expression can help the reader of your code recognize when they need to pay attention. In the example from my Perl Monks note, I talk about variations in intent expressed by different conditional expressions in Perl.
Idiom can also serve as a warning. An unusual or advanced idiom should tell the reader that he is entering dangerous territory. This is a message that it's time to pay close attention. A friend of mine is fond of referring to advanced code with the phrase Here there be dragons like on old maps. A properly placed idiom can do that more effectively than a comment. More importantly, if a good programmer runs into an new or unusual idiom, he should know to tread carefully and pay attention.
I think that idiomatic programming is a lot like becoming fluent in a natural language. You may be able to communicate with someone from Mexico using your high school Spanish. But going beyond simple, straight-forward communication will probably take a lot of effort. In some cases, that's enough. You can be mostly right when asking where is a restroom or how to find a taxi. But if you needed to argue a business or legal case or explain a medical condition, I'm sure you would want every possible nuance of the language at your disposal.
Programming is one of the most complex things that humans do. You have to be able to take complex concepts and translate them in such a way that they communicate to two very different audiences: an incredibly literal device and an intelligent person who may need to make subtle changes later. Explaining the instructions correctly to the computer is hard. Communicating your intent to the other programmer is even worse. If that programmer misunderstands your intent, he might ruin all of your carefully thought-out work. Well-chosen idioms can help in communicating that intent.