Over the years I’ve hopped back and forth between static and dynamically typed languages, trying to find the sweet spot. I currently still favor managed, static languages like C# and Java. But I agree that sometimes I have to write a whole lot of code to express my intent to the compiler without any great benefit. And no, i don’t think that code-generation is a way out of this.
What’s not to like about static?
I won’t go over the usual arguments for dynamic, which basically boil down to “you can do what you want without having to explain it to the type system first”. I’ll stipulate that that is why most people choose dynamic, but it’s not a significant a pain point with static for me. I did spend a good many years in dynamic land and switched to static of my own free will. Instead, I want to concentrate on some specific cases.
Fine grained basic types are usually overkill
I generally don’t care whether i am dealing with
decimal. For the most part,
number would do just fine. I think these types can be useful optimizations for both speed and memory, but certainly something that would be better optimized by a tracing rather than declaratively at compile time. And having to call special converters all over the place to go between these various types, is just not useful. I think type inference can handle these scenarios just fine.
Execution speed is a red-herring
Declaration and Discoverability of Dependencies
So what’s the pain point of dynamic for me? I care neither about the locking down of a class to handle only statically defined things, nor about the guarantee that a type is really a particular type. Frankly “types” are not important to me. However, declaration of dependencies in a discoverable fashion is!
What do I mean by that? A class should tell me via a machine discoverable contract what it expects the passed instance to be capable of. If I use a class that has service dependencies at construction time, or instance dependencies at method invocation time, I want to be able to discover this in code, rather than by looking at documentation or going by naming convention. After all, hasn’t documentation been deemed a code smell? Why is it then, that in dynamic languages the expected capabilities of the object to be passed is not expressed in a fashion that can be discovered without breaking encapsulation and looking at what the code expects to do with the passed instance?
Sure, dynamic languages pride themselves on not requiring an IDE. This is often held up as a strength and a key reason why they are faster to develop in. In my experience, however, I find dynamic languages faster for small things but as the project grows, my velocity decreases:
- I have to memorize more and more code and refer back to docs more, instead of letting the IDE guide me on available signatures
- Instead of navigating by concrete signatures, i do lots of string searches
- Instead of using long, self-documenting class and method names, I use terse syntax to ease typing and recollection
- Instead of symbolic refactoring, I do regex replaces, hoping there isn’t a syntax collision between classes that replaces the wrong thing
- And handing off code or integrating someone else’s code is slower because domain knowledge moves out of the code into tribal knowledge and documentation
Declaring Requirements instead of Capabilities
All the above is not a problem in static languages, but at the cost of inflexible, rigid types. Types are a solution that are a trojan horse of limitations that are completely orthogonal to the problem of dependency discovery. A class requiring an object of type User should have no dependence on the implementation details of User. Having such a dependence would be a clear violation of encapsulation. The class should simply want an instance of an object that has the capabilities of a User, i.e. it has a requirement for an object that exposes certain capabilities. The class should be able to declare a contract for the instance to be passed in.
In C# and similar languages this contract is an Interface. Interfaces allow the declaration of capabilities without an implementation. In interface inheritance, a class commits to providing the contract expressed by the interface. So a class requiring an a specific interface can declare its requirements without any knowledge of implementation. All right, problem solved! Right?
Interfaces as Contracts are upside down
Interfaces unfortunately do not solve the problem, because the way the are attached to implementation inverts the dependence hierarchy. I.e. User implements an interface its author declared, called IUser. Now IUser becomes my dependency, which is still a declaration outside of my control. I should not care where the implementation comes from. But an interface, puts the burden on a third party to implement my interface, which means I cannot use anything pre-existing, since it wouldn’t have implemented my interface, or the burden is put on the third party to provide an interface tome to use, which means another third party solving the same problem, provides their own interface.
This may be wonderful for mocking and unit testing, but it still ties me to a contract not of my own making and usually violates the Interface Segregation Principle: Clients should not be forced to depend upon interfaces that they do not use. So interfaces provide a solution, but they still enforce rigidity that has no benefit to the definition of dependency contracts.
Contracts for dependencies
At the end of the day, I have less to quarrel about dynamic vs. static, than tribal definition (naming conventions, documentation, etc.) of dependencies vs. declarative definition of dependencies. Until I can discover what a class expects as its input without being told or cracking open the man page, I will suffer the yoke of interfaces. Especially since I can still use Dynamic Proxies to fake a class implementing an interface — in yet another “more code than you’d think” way of working, tho.
Are there any static or dynamic languages that have tackled declarative contracts that are not attached at the implementation side that I’m not aware of? It seems like a sweet spot that isn’t yet addressed.
Update: I realize i left those wanting a solution to the interface issue in C# wanting. There are two ways to solve the problem that I’m aware of, Duck Typing as offered by LinFu and delegate injection, both of which I will cover in future posts.