Monday, August 10, 2009

Static Typing gives you a head start, Tests help you finish

In one of my earlier posts (almost a year back) I had indicated how type driven modeling leads to succinct domain structures that inherit the following goodness :

  • Lesser amount of code to write, since the static types encapsulate lots of business constraints

  • Lesser amount of tests to write, since the compiler writes them implicitly for you


In a recent thread on Twitter, I had mentioned about a comment that Manuel Chakravarty made in one of the blog posts of Micheal Feathers ..

"Of course, strong type checking cannot replace a rigorous testing discipline, but it makes you more confident to take bigger steps."

The statement resonated my own feelings on static typing that I have been practising for quite some time now using Scala. Since the twitter thread became louder, Patrick Logan made an interesting comment in my blog on this very subject ..

This is interesting... it is a long way toward the kind of explanation I have been looking for re: "type-driven programming" with rich type systems as opposed to "test-driven programming" with dynamic languages.

I am still a big fan of the latter and do not fully comprehend the former.

I'd be interested in your "type development" process - without "tests" of some kind, the type system may validate the "type soundness" of your types, but how do you know they are the types you actually *want* to have proven sound?


and the conversation became somewhat longer where both of us were trying to look into the practices and subtleties that domain modeling with type constraints imply on the programmer. One of the points that Patrick raised was regarding the kind of tests that you would typically provide for a code like this.

Let me try to look into some of the real life coding that I have been using this practice on. When I have a code snippet like this ..

/**
 * A trade needs to have a Trading Account
 */
trait Trade {
  type T
  val account: T
  def valueOf: Unit
}

/**
 * An equity trade needs to have a Stock as the instrument
 */
trait EquityTrade extends Trade {
  override def valueOf {
    //.. calculate value
  }
}

/**
 * A fixed income trade needs to have a FixedIncome type of instrument
 */
trait FixedIncomeTrade extends Trade {
  override def valueOf {
    //.. calculate value
  }
}
//..
//..

/**
 * Accrued Interest is computed only for fixed income trades
 */
trait AccruedInterestCalculatorComponent {
  type T

  val acc: AccruedInterestCalculator
  trait AccruedInterestCalculator {
    def calculate(trade: T)
  }
}


I need to do validations and write up unit and functional tests to check ..

  • EquityTrade needs to work only on equity class of instruments

  • FixedIncomeTrade needs to work on fixed incomes only and not on any other instruments

  • For every method in the domain model that takes an instrument or trade, I need to check if the passed in instrument or trade is of the proper type and as well write unit tests that check the same. AccruedInterestCalculator takes a trade as an argument, which needs to be of type FixedIncomeTrade, since accrued interest is only meaningful for bond trades only. The method AccruedInterestCalculator#calculate() needs to do an explicit check for the trade type which makes me write unit tests as well for valid as well as invalid use cases.


Now let us introduce the type constraints that a statically typed language with a powerful type system offers.

trait Trade {
  type T <: Trading
  val account: T

  //..as above
}

trait EquityTrade extends Trade {
  type S <: Stock
  val equity: S

  //.. as above
}

trait FixedIncomeTrade extends Trade {
  type FI <: FixedIncome
  val fi: FI

  //.. as above
}
//..


The moment we add these type constraints our domain model becomes more expressive and implicitly constrained with a lot of business rules .. as for example ..

  1. A Trade takes place on a Trading account only

  2. An EquityTrade only deals with Stocks, while a FixedIncomeTrade deals exclusively with FixedIncome type of instruments


Consider this more expressive example that slaps the domain constraints right in front of you without them being buried within procedural code logic in the form of runtime checks. Note that in the following example, all the types and vals that were left abstract earlier are being instantiated while defining the concrete component. And you can only instantiate honoring the domain rules that you have defined earlier. How useful is that as a succinct way to write concise domain logic without having to write any unit test ?

object FixedIncomeTradeComponentRegistry extends TradingServiceComponentImpl
  with AccruedInterestCalculatorComponentImpl
  with TaxRuleComponentImpl {

  type T = FixedIncomeTrade
  val tax = new TaxRuleServiceImpl
  val trd = new TradingServiceImpl
  val acc = new AccruedInterestCalculatorImpl
}


Every wiring that you do above is statically checked for consistency - hence the FixedIncome component that you build will honor all the domain rules that you have stitched into it through explicit type constraints.

The good part is that these business rules will be enforced by the compiler itself, without me having to write any additional explicit check in the code base. And the compiler is also the testing tool - you will not be able to instantiate a FixedIncomeTrade with an instrument that is not a subtype of FixedIncome.

Then how do we test such type constrained domain abstractions ?

Rule #1: Type constraints are tested by the compiler. You cannot instantiate an inconsistent component that violates the constraints that you have incorporated in your domain abstractions.

Rule #2: You need to write tests for the business logic only that form the procedural part of your abstractions. Obviously! Types cannot be of much help there. But if you are using a statically typed language, get the maximum out of the abstractions that the type system offers. There are situations when you will discover repetitive procedural business logic with minor variations sprinkled across the code base. If you are working with a statically typed language, model them up into a type family. Your tests for that logic will be localized *only* within the type itself. This is true for dynamically typed languages as well. Where static typing gets the advantage is that all usages will be statically checked by the compiler. In a statically typed language, you think and model in "types". In a dynamically typed languages you think in terms of the messages that the abstrcation needs to handle.

Rule #3: But you need to create instances of your abstractions within the tests. How do you do that ? Very soon you will notice that the bulk of your tests are being polluted by complicated instantiations using concrete val or type injection. What I do usually is to use the generators that ScalaCheck offers. ScalaCheck offers a special generator, org.scalacheck.Arbitrary.arbitrary, which generates arbitrary values of any supported type. And once you have the generators in place, you can use them to write properties that do the necessary testing of the rest of your domain logic.

27 comments:

Dean Wampler said...

Great post. Summarizes nicely what I like most about static typing and how it changes the way you approach problems.

patrickdlogan said...

I'll spend some time thinking about this example wrt a dynamic language. My initial reaction, though, is that I never write tests in a dynamic language that checks the "type" of a value. So I am unsure of the volume of tests that you claim to be saving through type checking.

I'm going to have to try to demonstrate this one way or the other though.

patrickdlogan said...

Quoting: "You need to write tests for the business logic only that form the procedural part of your abstractions. Obviously! Types cannot be of much help there."

The only tests I would expect to write using a dynamic language are "business logic" tests.

Can you, indeed, provide or even imagine an example where the business logic would be correct, but the types would be incorrect?

Is this from a code base that is publicly available? I'd like to look at an entire solution, if possible.

Unknown said...

Patrick -

I have mentioned in the post that it is not natural to think in terms of "types" in a dynamic language. Rather we think in terms of messages to which an abstraction needs to respond to. Hence you find typical checks in Ruby code like ..

obj.respond_to?(:length) && obj.respond_to?(:at)

which I think can be expressed more implicitly in static typing. Also in case of Ruby, lots of messages are responded to using method_missing, in which case responds_to will return false. Solution ? Write unit tests.

My basic point is that with static typing you need to do less runtime checks and write less unit tests to check some of the domain constraints. The compiler does them for you.

Thanks.

Unknown said...

Patrick -

Your last comment crossed mine.

In my blog post, I just summarized my experience with static typing, if it is done well, as in Scala. I find it quite convenient when I can express some of the domain constraints explicitly through types.
I am, in no way trying to force any conclusion :). It's very much a matter of taste. I just find Scala's type system quite expressive enough for such cases.

Thanks.

kofno said...

I disagree that #respond_to? and #method_missing produce more tests. If these calls exists in code, they exist to support the "business logic". Therefore, if my business logic passes, the "type checking" is implicitly verified.

Unknown said...

kofno -

It's not that #responds_to produces more tests. My point is that they introduce additional runtime checks, which static typing implicitly abstract.

Just like I said in my earlier comments, I find it convenient to design my model with such abstract constraints using types. Dynamic type based modeling offers a different paradigm based on duck typing. With Scala u can have the benefits of duck typing along with static checking. It's just a matter of personal choice that I find it more succinct.

Thanks.

patrickdlogan said...

"personal choice" - yes, I've believed for some time this is what it comes down to. But when I see static typers claiming a savings, I need to know why. I agree, it still seems to be a personal choice.

"Hence you find typical checks in Ruby code like ..

obj.respond_to?(:length) && obj.respond_to?(:at)"

I should hope these are used rarely indeed.

Unknown said...

But when I see static typers claiming a savings, I need to know why.

If you are given charge of maintaining a large codebase wouldn't you love to see models that are explicitly typed ? The domain constraints are so well advertised without even going into the guts of the business logic. Just curious!

Phil said...

I have to agree with Patrick; saying the "typical" Ruby projects contains a lot of calls to respond_to? sounds fairly straw-man-ish. You generally only see those in My First Rails(tm) projects by people coming from static languages.

This article does a good job of showing that static typing helps you avoid a large class of errors, but they're not the kind of errors I'm actually in any danger of making.

patrickdlogan said...

"If you are given charge of maintaining a large codebase wouldn't you love to see models that are explicitly typed?"

After 25+ years w/Lisp, Smalltalk, C, Pascal, C++, et al. all I can say is I have seen large success and large failures with both static and dynamic applications.

That may be different with better types like Haskell or Scala, but I have yet to see evidence.

In fact the glimpses of large systems I've seen with Haskell suggest other factors overwhelm the presence of richer types. Such as simple, well-written code and tests.

kofno said...

wouldn't you love to see models that are explicitly typed ? The domain constraints are so well advertised without even going into the guts of the business logic.

Perhaps this is subjective. I think that value is derived from the abstractions and the naming.

That being said, you've drifted a bit far afield from your original premise, which I believe was that static typing will save you from writing a whole slew of tests. I remain unconvinced.

The only tests you've identified as savings are ones that I've never found a need to write. They are implicitly verified by the tests I do write.

Unknown said...

I am not a Ruby or Rails expert. But a quick glance through the source code of Rails will give an idea of the various places that use respond_to or is_a.
Not that there is anything wrong with it - the way Ruby's reflective meta-programming works makes these things necessary. You will find places where they use method_missing and respond_to? to rewrite/generate methods on the fly. While this may be very flexible and takes great advantage of Ruby's MOP and conciseness, much of it can be modeled using a powerful type system. Look at Lift, the Scala Web framework. David Pollak had also blogged on his experience migrating from a Rails platform to Lift. Have a look at his experiences in http://blog.lostlake.org/index.php?/archives/45-A-real-world-use-of-lift.html. Very much resonates to what I found in practice with Scala.
Though it's still a matter of personal choice, but I still think many of domain invariants are modeled implicitly using a typed paradigm. And in those cases, the compiler does your unit testing.

patrickdlogan said...

"the way Ruby's reflective meta-programming works makes these things necessary"

method_missing? coresponds to smalltalk's doesNotUnderstand: which has a long history of disciplined uses. responds_to and is_a should be far less used in well-written systems.

In any of these cases, they should show up in "system" software (e.g. frameworks like rails, development tools) significantly more often than in end-user applications (i.e. that run _on_ rails).

"much of it can be modeled using a powerful type system. Look at Lift, the Scala Web framework"

That's fine. I have no trouble with good type systems being able to accommodate more of these mechanisms that have been used in dynamic languages for decades. The only problem I have is with the unsubstantiated assumptions that the typed mechanisms are somehow better.

All things being equal, sure, I'd like my potential errors checked asap. However from what I have experienced and seen, all things are not equal, and there are benefits of dynamic languages that outweigh the costs of static type checking.

As we all seem to be agreeing, these things are personal and subjective. But I'd like the static typers to have evidence when they claim "reduced testing" - as kofno, phil, and I state from experience, the tests that are claimed to be eliminated are not tests experienced programmers deem necessary to write in the first place. And so what's saved?

"Look at Lift, the Scala Web framework. David Pollak had also blogged on his experience migrating from a Rails platform to Lift"

There's not much in the blog post substantiating the claims for static type checking. I see issues with CRuby's concurrency - what about JRuby? what about erlang?

And I see issues with certain _design_choices for rails (e.g. naming conventions), and I see claims about the applicative style. (Again, what about erlang? what about lisp? These are applicative as well as dynamic.)

So I just don't see this as anything more than personal preference. All my experience across many languages tells me that the team counts much more than the language or the tools, but those count too.

Static type theories and implementations are getting better and someday I expect them to be so much better that there will be little difference between a "static" and a "dynamic" language. They'll get there.

Cedric said...

One example where static typing helps in the testing department is in refactoring.

It's quite possible to refactor dynamically typed code and have all your tests pass while you did break something that is not covered by your tests.

If your refactoring involves some type alteration (including method signature changes), a compiler won't even let you run that code until it's correct in all places.

Unknown said...

"as kofno, phil, and I state from experience, the tests that are claimed to be eliminated are not tests experienced programmers deem necessary to write in the first place. And so what's saved?"

Let me try to substantiate here what I mean by reduced number of tests .. maybe what I am calling tests are handled differently in a large project developed using dynamically typed languages.

Suppose we have the following abstraction. I cite the example in Scala ..

trait AccuredInterestCalculator {
  type T <: FixedIncome
  type U <: InterestCalculationConvention

  def calculate(instrument: T, convention: U) {
    //.. logic
  }
}

The method calculate takes 2 arguments which are bounded types. The instrument has to be a FixedIncome and the convention has to be an instance of InterestCalculationConvention. This we say are the preconditions that need to be satisfied before the method can execute. With a statically typed language we need not write explicit unit tests to check that passing an incorrect type of instrument should throw a blah blah exception. How do u do such precondition validations in dynamically typed languages ? I was assuming that many of these tests are required to check pre-conditions or post-conditions of any method invocation in a dynamically typed language. Am I wrong ?

patrickdlogan said...

"One example where static typing helps in the testing department is in refactoring."

True, but still this is a question of small degrees rather than orders of magnitude.

The first significant, and still one of the best and most complete, refactoring tools is the one for smalltalk, the "refactoring browser".

So again, the decision of refactoring being better in a static language comes down more to personal preference and comfort than anything else.

patrickdlogan said...

"With a statically typed language we need not write explicit unit tests to check that passing an incorrect type of instrument should throw a blah blah exception. How do u do such precondition validations in dynamically typed languages?"

Generally there is no reason to care *what* is passed in. The important aspect of any solution is this: are the calculations for the specific investment instruments correct or not.

Can you provide a scenario where the calculations would be correct using an instance of a "wrong" class? That just does not turn out to be a problem in practice. The preconditions that matter are *computational* rather than type/structural. Like you wrote yesterday - the purpose of "duck typing" is to get something that quacks correctly, not to get an object of type Mallard per se.

If you are using a dynamic language and you find yourself depending on something being "is_a" Mallard or "if responds_to waddle then..." then you are doing something *very* *very* *very* wrong in almost every case. These are things that should aalmost never matter.

Unknown said...

"Generally there is no reason to care *what* is passed in. The important aspect of any solution is this: are the calculations for the specific investment instruments correct or not."

I get what you say. But unfortunately the common practice in many of the dynamic languages has been towards doing explicit testing of the input to ensure that the object received is of the correct type. A quick googling will show lots of unit tests that do the following :

def test_create_should_work_correctly
  # We only want to test this if we test an ActiveRecord::Base class.
  return if not @model_class.new.respond_to?(:save)
...

For example this thread on StackOverflow explicitly recommends such practice .. http://stackoverflow.com/questions/497809/how-do-you-test-whether-a-model-has-a-given-method-in-rails

Have a look at this thread from Ruby talk that discusses the viability of having safety of viability along with the flexibility of duck typing. I am not sure if this is something exclusively used in Ruby. But my findings have been based on such observations only.

Channing Walton said...

Isn't it the case the introducing types as shown in the blog, tells anyone reading the code what is going on at a high level of abstraction. It is immediately obvious and apparent.

Am I right in thinking that to achieve the same understanding in a dynamic system, a reader would need to get into lower levels of detail?

Channing

patrickdlogan said...

"unfortunately the common practice in many of the dynamic languages has been..."

Most software written in most languages is mostly pretty bad. If you think this will change with Scala I believe you will be disappointed.

patrickdlogan said...

"tells anyone reading the code what is going on at a high level of abstraction."

The example tells me that a fixed rate instrument *has* an accrued interest calculator. If I were to look at similar code in a well written dynamic language program, I should expect to see very quickly that a fixed rate instrument *has* an accrued interest calculator, or at least a calculation.

What is interesting about this? Not much.

The interesting bits are not shown even in this Scala program. Because what is interesting about an investment portfolio is its value and how its value may or may not increase over time.

For fixed instruments its value comes in part from interest. And so the interesting aspect about accrued interest is *how* and *when* it is calculated.

These type declarations say *nothing* about *how* and *when*. And so these type declarations are not saying anything interesting.

Looking through the tests (including logical property assertions, e.g. in a QuickCheck like mechanism) I would expect to see interesting things about how and when interest is accrued whether the implementation language is static or dynamic.

Unknown said...

Patrick -

Thanks a lot for the detailed comments that you have been posting. It has truly been quite illuminating for me and have given me enough insights on how to think of modeling in a dynamically typed language. Unfortunately as u rightly mentioned, real life examples are often not the model ones and can be misleading.

I completely agree with you that the interesting stuff are always in the details and they are never assured by the type system. Tests precisely do that, and there is no denying the fact that we need to write tests irrespective of
whether we are doing dynamic or static typing.

With static typing I have the assurance that when I am looking at AccuredInterestCalculator in an application which compiles successfully, I get to know from the definition of the abstraction what types of objects it takes (and the constraints that they imply) and also at the same time be assured that *all* my use of the abstraction has the right types passed to them.

If you consider this to not to be of much value, it's fine. Once again, as we have been discussing throughout, it's very much a personal choice. If I were to maintain a large code base, to me this is valuable info. But again
perspectives differ :)

Channing Walton said...

I think there is great value in being able to quickly look at the system at a high level, particularly when you are learning about a system. Of course the devil is in the details but I personally find it helpful to have an overview of a system, particular a complex one, without having to read through tests to divine it.

All of this is moot of course, since most large systems are written so badly that we are doomed to wade through untested weirdness to establish what is going on.

patrickdlogan said...

I am about ready to leave it at, yes, this is a personal choice. But one more point...

"the interesting stuff are always in the details"

My point about fixed assets is that how interest is accrued is not at all a "detail". This is the single most important aspect of fixed assets. And so a language and a program should allow this to be *highlighted* rather than buried somewhere as a "detail".

Maybe what we consider important is different and a matter of choice. But in all OO languages *far* to much emphasis is given to *structure*. Bah! Structure is *incidental* at best.

Rajanikanth said...

Debasish,

Thanks for bringing this discussion(personal pursuit), imho, it is very important especially at this inflection in language evolution between strongly typed languages were/are so prevalent and on other side, type agnostic development getting rapidly popular & reliably solving seemingly same problems with great ease. I am fan of static typing with uncomfortable void of not being able to express exactly what it is that makes it better and can't succumb to it being just a feeling. May be it is, need to explore more.

Patrick,

A favor, what is the most type agnostic functional language you have used so far?

Rajanikanth said...

Correction meant 'most productive type agnostic..'