Wednesday, July 07, 2010

explicit is better than implicit: in favor of static typing

Right now, static not dynamic typing more closely fits my aesthetic preferences and intellectual biases. And the best expression of the reason is "explicit is better than implicit". The primary problem I have with dynamic typing, i.e. checking types solely as the program executes, is that the type must be left implicit/unknown despite its vital importance to correct reasoning about the code's operation. The crux is whether the upsides of a type being mutable and/or loosely-checked outweigh the downside of it being implicit.

Dynamism. I'm inclined to guess that most of the time most developers don't in fact require or exploit the admittedly-vast possibilities that dynamic typing enables. The effectiveness of tracing compilers and run-time call-site optimizations confirms this. My experiences with C#'s "var" have demonstrated that, for mere avoidance of type declarations for strictly-local variables, type inference almost always works as well as a pure dynamic type. Stylistically speaking, rebinding a name to multiple data types probably has few fans. The binding of a name to different data types is more handy for parameters and returns...

Ultimate API Generality. As for the undeniable generality of dynamically-typed APIs, I'm convinced that most of the time utterly dynamic parameters are less accurate and precise than simple parameters of high abstraction. This is seen in LINQ's impressive range of applicability to generic "IEnumerable<T>" and in how rarely everyday Objective-C code needs to use the dynamic type id. With few exceptions, application code needs to implicitly or explicitly assume something, albeit very little, about a variable's contents in order to meaningfully manipulate it. In languages with dynamic typing, this truth can be concealed by high-level data types built-in to the syntax, which may share many operators and have implicit coercion rules. Of course, in actuality the API may not react reasonably to every data type passed to it...

"Informal Interfaces". According to this design pattern, as long as a group of objects happen to support the same set of methods, the same code can function on the entire group. In essence, the code's actions define an expected interface. The set of required methods might differ by the code's execution paths! This pattern is plainly superior for adapting code and objects in ways that cut across inheritance hierarchies. Yet once more I question whether, most of the time, the benefits are worth the downside in transparency. Every time the code changes, its informal interface could change. If someone wants to pass a new type to the code, the informal interface must either be inferred by perusing the source or by consulting documentation that may be incomplete or obsolete. If an object passed to the code changes, the object could in effect violate the code's informal interface and lead to a bug that surprises users and developers alike. "I replaced a method on the object over here, why did code over there abruptly stop working?" I sympathize with complaints about possible exponential quantities of static interface types, but to me it still seems preferable to the effort that's required to manually track lots of informal interfaces. But in cases of high code churn, developers must expend effort just to update static interface types as requirements and objects iterate...

Evolutionary Design. There's something appealing about the plea to stop scribbling UML and prod the project forward by pushing out working code regardless of an anemic model of the problem domain. In the earliest phases, the presentation of functioning prototypes is a prime tool for provoking the responses that inform the model. As the model evolves, types and type members come and go at a parallel pace. Isn't it bothersome to explicitly record all these modifications? Well, sure, but there are no shortcuts around the mess of broken abstractions. When the ground underneath drops away, the stuff on top should complain as soon as possible, rather than levitating like a cartoon figure until it looks down, notices the missing ground, and dramatically plummets. Part of the value of explicit types lies precisely in making code dependencies not only discoverable but obvious. This is still more important whenever separate individuals or teams develop the cooperating codebases. The other codebase has a stake in the ongoing evolution of its partner. A "handshake" agreement could be enough to keep everyone carefully synchronized, but it's more error-prone compared to an enforced interface to which everyone can refer. During rapid evolution, automated type-checking is an aid (although not a panacea!) to the task of reconciling and integrating small transforming chunks of data and code to the overall design. Types that match offer at least a minimum of assurance that contradictory interpretations of the domain model haven't slipped in. On the other hand, unrestricted typing allows for a wider range of modeling approaches...

Traits/Advanced Object Construction. No disagreement from me. I wish static type schemes would represent more exotic ideas about objects. Still, most of the time, applied ingenuity, e.g. OO design patterns, can accomplish a lot through existing features like composition, delegation, generics, inheritance.

I want to emphasize that my lean toward static typing for the sake of explicitness isn't my ideal. I direct my top level of respect at languages and platforms that leave the strictness of the types up to the developer. I like a functioning "escape hatch" from static types, to be employed in dire situations. Or the option to mix up languages as I choose for each layer of the project. I judge types to be helpful more often than not, but I reserve the right to toss 'em out when needs demand.

No comments:

Post a Comment