Types and Metaprogramming: Can We Have Both?

July 21, 2009

Michael Feathers has posted a an excellent article called Ending the Era of Patronizing Language Design. You should go read the whole article, but the crux of the arguments boils down to:

The fact of the matter is this: it is possible to create a mess in every language. Language designers can't prevent it. All they can do is determine which types of messes are possible and how hard they will be to create. But, at the moment that they make those decisions, they are far removed from the specifics of an application. Programmers aren't. They can be responsible. Ultimately, no one else can.

I agree with Michael, but I see it from a slightly different viewpoint. The most common argument for static typing is that it will prevent you from shooting yourself in the foot. If your program has syntax errors or type errors, the compiler will catch them for you. These means that you can write less tests than you would need to write for the same program written in a dynamically typed language.

This argument I completely disagree with. Having a spell checker doesn't mean you can proofread less. No amount of spell checking and grammar checking is going to prevent your writing from having errors. If you don't test your program, it will have errors.

I like looking at static typing in a more "glass is half full" sort of way. The bigger benefit of static typing is that the type system can be used as a mechanism to make the code more self-descriptive. Here's an analogy from Real World Haskell:

A helpful analogy to understand the value of static typing is to look at it as putting pieces into a jigsaw puzzle. In Haskell, if a piece has the wrong shape, it simply won't fit. In a dynamically typed language, all the pieces are 1x1 squares and always fit, so you have to constantly examine the resulting picture and check (through testing) whether it's correct.

I think the jigsaw puzzle analogy is even better if you think about it a different way. Think of a simple puzzle that is made up of only nine pieces. If each piece is a 1x1 square, you have to look at the part of the picture on each piece to determine where it goes. If you had a puzzle with nine uniquely shaped pieces, you could solve the puzzle without looking at the picture on each piece at all. You could assemble the puzzle simply by connecting the piece that fit together.

When analyzing a program, "looking at the picture on the piece" means reading the body of a method. Let's take a look at some Ruby code without looking at the body of the method. Take for example, these two method declarations:

def select(*args)
def select(table_name=self.class.table_name, options={})

The first method gives us no hint as to what this method is, whereas in the second example, we can start to see what's going on with this method. What if the method signature looked like this:

SQLQueryResult execute(SQLQuery query)

It should be obvious what this does. So it should be clear that having types in our method signatures is not the language's way of making sure we don't make errors, but instead is a means for expressing the intent of how the code works and how it should be used.

The problem is that this is not the only way of making code more self-descriptive or easier to reason about. The authors of Structure and Interpretation of Computer Programs state:

A powerful programming language is more than just a means for instructing a computer to perform tasks. The language also serves as a framework within which we organize our ideas about processes. Thus, when we describe a language, we should pay particular attention to the means that the language provides for combining simple ideas to form more complex ideas. Every powerful language has three mechanisms for accomplishing this:

  • primitive expressions, which represent the simplest entities the language is concerned with,

  • means of combination, by which compound elements are built from simpler ones, and

  • means of abstraction, by which compound elements can be named and manipulated as units.

The meta-programming capabilities of Ruby, which are on par with that of any other language, including Lisp, provide a powerful means of abstraction. ActiveRecord is a clear example of that:

class Order < ActiveRecord::Base
  belongs_to :customer
  has_many :line_items
end

Can statically typed languages provide a means of abstraction on par with that of dynamic typed languages? Steve Yegge doesn't think so. I'm not sure and I am in constant search of that answer. The combination of meta-programming, object-orientation and functional programming in Ruby provides a powerful means of abstraction. I would love to have all of that as well as a type system, so I would hate to see the programming community completely give up on static typing and just declare that dynamic typing has won.

Posted in Technology | Tags DynamicTyping, Haskell, StaticTyping, Ruby

Comments

1.

I'm not going to claim that C++ is a great language, or that it gives you the right safety nets, or that it prevents you from shooting yourself in the foot. Let me be abundantly clear about this: C++ is a language full of razors and shards of glass bent on tearing your fragile mind to shreds.

That said, the C++ templating language does a good job of providing significant metaprogramming functionality on top of static typing by being a Turing-complete language that actually operates on the type system itself. Think of the templating language as a duck-typed dynamic language that is executed at compile time to generate (i.e. metaprogram) the types used by the base C++ code.

If the templating language sat on top of a nicer statically typed language (honestly can't think of one that doesn't have ugly C legacy), it could be the language you're hoping for.

# Posted By Greg on Tuesday, July 21 2009 at 11:32 AM

2.

@Greg,

Compile-type metaprogramming seems like it could provide the best of both worlds. I know Scala has support for compiler plug-ins, but I'm not sure if it's practical to use that for these kinds of things.

# Posted By Paul Barry on Tuesday, July 21 2009 at 2:45 PM

3.

Have you seen Template Haskell?

# Posted By Shae Erisson on Tuesday, July 21 2009 at 3:29 PM

4.

One of the big problems with combining free-reign reflection and type systems, is that reflection breaks parametricity. Reconciling this is actually a big area of research in type theory today, particularly for GADTs and Dependent Types. Type indices are nice for things like compile-time bounds checking and dynamic type-inference, and parametricity is nice for things like writing generic code and doing program transformations (optimizations), but they interfere with and undermine one another.

As for the question about whether typed languages can ever be as powerful as untyped languages, the short answer is "no": the untyped lambda calculus is Turing complete, whereas typed lambda calculi are strongly normalizing (in the term layer) and thus are not. However, powerful typed lambda calculi regain some of that expressivity by moving non-termination into the type layer. And it's a somewhat dubious claim that programming languages should be Turing complete in the first place (since even the hardware they run on isn't).

I think the real question that needs to be asked here is what is actually meant by "metaprogramming", "reflection", and "introspection". That is, what is the problem that we really want to solve (not how do we want to go about solving it). In untyped and weakly-typed languages reflection is used to make up for a lack of parametricity and genericity. In OOP languages inheritance is often used to make up for a lack of parametricity. In languages like Haskell we can use parametric polymorphism. In languages like MetaOCaml we can use staged compilation. The level of dynamism and the range of bugs and optimizations varies dramatically between these different approaches, but they're all trying to solve the same basic problem. In my experience, when folks from the Ruby or Python communities are talking about "metaprogramming" they're using the term to mean "anything outside of 'normal' programming" rather than to mean something specific like staged programming, type-level programming, or the like.

# Posted By wren ng thornton on Tuesday, July 21 2009 at 9:43 PM

5.

@wren

Sorry, I should have been more clear about what I meant by metaprogramming. See my next post for more details:

http://paulbarry.com/articles/2009/07/21/in-search-of-sharper-tools

# Posted By Paul Barry on Tuesday, July 21 2009 at 11:51 PM

Comments Disabled