4/7/09

Spare change at the whole Static-vs-Dynamic thingy


(NOTE: I'm a software developer, I have a blog, clearly I MUST write about static-vs-dynamic ... I guess soon I'll be writing a monads tutorial)

With all the fuss about Twitter migrating (the back-end) from Ruby to Scala (w00t!), the whole static-vs-dynamic debate has been re-ignited.
As a disclaimer, I'm in the static typing "camp". I've tried Ruby but didn't spark anything. In "dynamic languages", I have more experience with JavaScript (and for the record, I think is awesome: a true underdog of programming languages).
While I can appreciate the freedom and speed of development of dynamic languages, and I think is a matter of personal styles than about which one is better ("choose your own poison"), I think static typing suits better my frame of mind while creating software.
Anyhow, I felt something was missing in the debate and Twitter's use of "kind_of?" points to one of the reasons for static typing I been trying to articulate for a while.
(First and foremost: if you're going to complain "static typing forces me to add a lot of boilerplate and cruft", look into Haskell, ML, Scala, or F# and then come back or you'll force me to do evil things to cute creatures. And if you want to learn how a type system can be used to PROVE that a program is correct, look into Coq)

Types reduces the possible combinations between components
Any software program can be seen as a system of interconnected elements (modules/functions/objects, etc.), by adding type information you're restricting the ways those elements can connect to each other and the possible execution paths of the system. By doing that, you're loosing some of the valid combinations, you're also trimming many invalid ones.
The argument is with good unit tests you can check for the same things, but you have to actually write the code for that. While you should have unit tests for your module, without type restrictions, you have to write and test more execution paths. Moreover, writing the test, executing it and analyzing the result are separate steps, where your brain has to switch contexts.

Types as a Poka-Yoke
There's a lot of influence of Lean Manufacturing in software development (all the "Agile" stuff ), and from that perspective, having more restrictions on how you can use the available components acts like a poka-yoke mechanism: "a behavior-shaping constraint, or a method of preventing errors by putting limits on how an operation can be performed in order to force the correct completion of the operation". The same Lean / Total Quality Management recommends catching defects as early as possible and not use testing as a product quality measure. How more agile can you be when you don't have to write and run test to achieve a basic level of integration quality?

Types as (kind-of) contracts
By using static typing you get self-documenting self-enforcing operation contracts (for a price)
. I believe the larger and more complex the system, the more static typing can help than hinder your work.
NASA could have saved some money if they were able to write:

class MarsObiter {
def setThrust(v:Thrust[MetricUnits])= ...

There's no way you can use a Thrust[ImperialUnits] instead. (For extra points, you can define an implicit conversion in Scala :D )
But even if you have a basic type system like Java, don't just suffer it, use it at your advantage, create the types that can help you to write better code.
Check Stephan's excellent post on what do you loose if you use only primitive types with no semantic meaning (or no types at all)

Enough!
But enough babbling, I can show some examples using the "lowly" Java language:
Suppose we have a method to book a shipment for a customer and a container on a specific date.
Let's start without any typing information:


public bookShipment(customerId, containerId, shipmentDate){...


Totally valid, but... is "1234-321" a valid customerId? can I use a string as shipmentDate? in which format? What I get back?
Let's improve things a little by adding some basic typing information:


public boolean bookShipment(int customerId, int containerId, Date shipmentDate){...


Well, now I now that "X123Z" is not a valid customer or that "MRSK0023" is not a valid container, the date must be a proper date object, and it doesn't take a PhD to infer that will return true if the booking is successful.
But still, nothing prevents me to swap customerId with containerId and get a nasty but subtle bug. Wouldn't be nice if the compiler can prevent that? Well, it can if you don't use primitives and use more domain meaningful types, giving you a DDD flavor (and that's a win-win situation)


public boolean bookShipment(Customer customer, Container container, Date time){...


By examining the signature we know we can't pass a Container when a Customer is expected, and the method reflects better the domain of the application and tries to move towards "an ubiquitous language". But we can improve that:


public bookShipment(customer, container, shipmentDate) { ...


Here Hindley-Milner type inference takes care of identifying the input and output paremeters. No need to declare a thing and still type safe!
(although for documentation/readability purposes, I prefer explicit type declarations, and seems that the Haskell community agrees... that's why I don't mind typing the types of methods in Scala)



The whole point about all this? Go back and get things done, use whatever suites you best and don't waste your time reading blogs and stuff ;-)
If you excuse me, I need to step down from my tittle soapbox and go to do some coding...
Post a Comment