Well, my comparative languages class in college said there were four factors for evaluating programming languages:
- Readability
- Writability
- Reliability
- Cost
I would add a 5th, which is maintainability. The argument I'm going to attempt to make today is that Dynamic Languages, and Ruby in particular, score better in these factors than static languages, and therefore are more powerful languages.
The first thing you might think of is syntax, and there is certainly something to be said for that. Clean syntax makes for enhanced readability and writability. And the ability to create DSLs in your language is another major plus to readability and writability. So right off the bat Ruby is off to a pretty good start.
But I think the argument actually goes deeper than that. Consider the SOLID design principles:
The first thing you might think of is syntax, and there is certainly something to be said for that. Clean syntax makes for enhanced readability and writability. And the ability to create DSLs in your language is another major plus to readability and writability. So right off the bat Ruby is off to a pretty good start.
But I think the argument actually goes deeper than that. Consider the SOLID design principles:
- SRP: Single Responsibility Principle
- OCP: Open Closed Principle
- LSP: Liskov Substitution Principle
- ISP: Interface Segregation Principle
- DIP: Dependency Inversion Principle
These are principles we use to describe good code. That is, more readable code, more reliable code, more maintainable code, and less costly code. And I believe that Ruby, as a language, has many of these principles built right in. And that means that it scores higher on the comparative ladder, and therefore is a better, more powerful, language!
Lets start with SRP. So Ruby has Modules, which are pretty great. And I would argue that they are a helpful tool for SRP. I've had some awesome conversations on this point, but I land on the side of saying that Modules and MixIns are totally useful for SRP. But that's about as far as we go for language support for SRP, so lets move on!
OCP is a bit more interesting, though still straight forward. Dynamic languages like Ruby allow you to open up any class from a distance and add new methods to them, or change the definitions of existing methods. So you can change anything in the class without having to open up the actual class definition. That's some pretty serious built in OCP support. Of course, to be fair, this isn't a feature you're likely to depend on when you're implementing a class you want to follow OCP... Its more of a last resort really, but we'll find its very useful for mocking which we'll talk about later.
When talking about LSP Wikipedia has this to say: "Behavioral subtyping is a stronger notion than typical subtyping of functions defined in type theory." This is a super-wonderful sentence for my purposes here! LSP is all about the behavior of subclasses matching their parent classes, and the beauty of this is that dynamic languages are ALL about behavior, you tend not to get to hung up with "types". So, you still have to be careful that your derived classes' behavior is consistent with that of their parent's, but since you're already thinking about behavior instead of types, you are off to a much better start.
Interface Segregation seems simple at first glance, dynamic languages don't have interfaces, end of story right? Not quite, because while ISP is about interfaces, what's its saying is that your interfaces should be highly cohesive so that you cannot divide the methods of an interface into discrete sets based on who calls them. This is because we don't want to be coupled to methods we don't care about. But in a dynamic language, the caller just sends whatever "messages" it wants to the object it is calling, so by default our "interfaces" are as segregated as they could ever possibly be! We have perfect, automatic ISP in dynamic languages.
Dependency Inversion is sort of the crowning jewel of built in principles in dynamic languages. Most of the time in static languages our main reason for caring about Dependency Inversion is for unit testing. We invert our dependencies so that we can mock them out in our tests. This is outrageously annoying because it means we can never ever ever call a constructor. So we are forced to either a) make all our dependencies stateless or b) wrap all our constructors with factories. It also means we have to introduce interfaces to describe a huge number of our classes. In Ruby, we don't have to think about this. At all. We can stub out the calls to constructors and return our own mock versions. It's so easy it's totally stunning the first time you do it.
So there you have it! Dynamic languages are awesome, in part, because they have the SOLID design principles built in, which makes them score better on the comparative language scale.
Lets start with SRP. So Ruby has Modules, which are pretty great. And I would argue that they are a helpful tool for SRP. I've had some awesome conversations on this point, but I land on the side of saying that Modules and MixIns are totally useful for SRP. But that's about as far as we go for language support for SRP, so lets move on!
OCP is a bit more interesting, though still straight forward. Dynamic languages like Ruby allow you to open up any class from a distance and add new methods to them, or change the definitions of existing methods. So you can change anything in the class without having to open up the actual class definition. That's some pretty serious built in OCP support. Of course, to be fair, this isn't a feature you're likely to depend on when you're implementing a class you want to follow OCP... Its more of a last resort really, but we'll find its very useful for mocking which we'll talk about later.
When talking about LSP Wikipedia has this to say: "Behavioral subtyping is a stronger notion than typical subtyping of functions defined in type theory." This is a super-wonderful sentence for my purposes here! LSP is all about the behavior of subclasses matching their parent classes, and the beauty of this is that dynamic languages are ALL about behavior, you tend not to get to hung up with "types". So, you still have to be careful that your derived classes' behavior is consistent with that of their parent's, but since you're already thinking about behavior instead of types, you are off to a much better start.
Interface Segregation seems simple at first glance, dynamic languages don't have interfaces, end of story right? Not quite, because while ISP is about interfaces, what's its saying is that your interfaces should be highly cohesive so that you cannot divide the methods of an interface into discrete sets based on who calls them. This is because we don't want to be coupled to methods we don't care about. But in a dynamic language, the caller just sends whatever "messages" it wants to the object it is calling, so by default our "interfaces" are as segregated as they could ever possibly be! We have perfect, automatic ISP in dynamic languages.
Dependency Inversion is sort of the crowning jewel of built in principles in dynamic languages. Most of the time in static languages our main reason for caring about Dependency Inversion is for unit testing. We invert our dependencies so that we can mock them out in our tests. This is outrageously annoying because it means we can never ever ever call a constructor. So we are forced to either a) make all our dependencies stateless or b) wrap all our constructors with factories. It also means we have to introduce interfaces to describe a huge number of our classes. In Ruby, we don't have to think about this. At all. We can stub out the calls to constructors and return our own mock versions. It's so easy it's totally stunning the first time you do it.
So there you have it! Dynamic languages are awesome, in part, because they have the SOLID design principles built in, which makes them score better on the comparative language scale.