Dynamic languages are an anti-pattern
I'm starting to think that it's much more difficult to write maintainable dynamically-typed code than it is to write maintainable statically-typed code. Many of the bugs I've been fixing lately would have caused type errors in a statically typed language.
5 comments
IMHO it depends on the part of the project.
When you are building infrastructure (buisness logic independent), static languages are better, because most things are clearly defined, and change rarely, so overhead is small, and the code is general (few special cases).
When you build the real thing, that includes business logic, dynamic languages are better, because it's much easier to change business logic and add special cases on the fly. And there are always special cases.
That's why big projects written in static languages often have implementations of some dynamic language embedded, and when they don't, they use unholy mess of configuration files and dependency injection to achieve the same goal.
I much prefer intentionaly designed embedded dynamic language to writing implementations of factories and changing xml files.
To see how much dynamic your static code is count all the casts from Object, and all template classes instanced for Object (especially common are List<Object>, Map<String, Object>).
Writing infrastructure code in dynamic languages - I agree - it's harder to get right than with staticaly typed languages, and the benefits aren't obvious.
When you are building infrastructure (buisness logic independent), static languages are better, because most things are clearly defined, and change rarely, so overhead is small, and the code is general (few special cases).
When you build the real thing, that includes business logic, dynamic languages are better, because it's much easier to change business logic and add special cases on the fly. And there are always special cases.
That's why big projects written in static languages often have implementations of some dynamic language embedded, and when they don't, they use unholy mess of configuration files and dependency injection to achieve the same goal.
I much prefer intentionaly designed embedded dynamic language to writing implementations of factories and changing xml files.
To see how much dynamic your static code is count all the casts from Object, and all template classes instanced for Object (especially common are List<Object>, Map<String, Object>).
Writing infrastructure code in dynamic languages - I agree - it's harder to get right than with staticaly typed languages, and the benefits aren't obvious.
Thanks everyone, reading through your answers and thinking through the bugs I've solved has lead me to some interesting conclusions.
(The language I'm using is Ruby).
The problem is not that Ruby isn't statically typed. It's that what I think of as type errors in Ruby pass without incident. The most irritating example of this is that if you don't pass enough arguments to a proc object the other parameters get filled in with nil. In my mind this should throw a type error, but in Ruby it doesn't.
Perhaps this is why Rubyists are so keen on testing, because their language is full of implicit behaviours.
(The language I'm using is Ruby).
The problem is not that Ruby isn't statically typed. It's that what I think of as type errors in Ruby pass without incident. The most irritating example of this is that if you don't pass enough arguments to a proc object the other parameters get filled in with nil. In my mind this should throw a type error, but in Ruby it doesn't.
Perhaps this is why Rubyists are so keen on testing, because their language is full of implicit behaviours.
Proc doesn't check arity. That is just how proc and Proc.new are expected to work. If you want to check the arity, use lambda or ->.
x = ->(a, b) { puts a, b }
x.call #ArgumentError: wrong number of arguments (0 for 2)
x = lambda { |a, b| puts a, b }
x.("just one argument") #ArgumentError: wrong number of arguments (1 for 2)
x = ->(a, b) { puts a, b }
x.call #ArgumentError: wrong number of arguments (0 for 2)
x = lambda { |a, b| puts a, b }
x.("just one argument") #ArgumentError: wrong number of arguments (1 for 2)
> The most irritating example of this is that if you don't pass enough arguments to a proc object the other parameters get filled in with nil. In my mind this should throw a type error, but in Ruby it doesn't.
Maybe automate checking this? I don't know Ruby much, but there's .arity method on proc that seems to be apropriate for this.
Maybe automate checking this? I don't know Ruby much, but there's .arity method on proc that seems to be apropriate for this.
Sorry, it wasn't a proc object, it was a block. The following is a potential fix, but it's irritating to have to write it.
blk = lambda { |arg1, arg2|
# do something
}
obj.some_method(&blk)I experienced the same, but its more the fault of the programmer. Dynamic languages are easier to enter ("learn") and thus attract more mediocre developers who deliver inferior code.
You had bugs because you didn't have adequate tests, not because you were using a dynamic language. That's the anti-pattern.
[deleted]
The problem is that to find the most annoying of these bugs using testing alone would have meant that so much time would have been spent writing tests that the productivity gains that you get writing in a dynamic language would have been nullified.
Which is why the importance of unit testing is underscored in the communities around dynamic languages.