Summary
If it is, maybe there's something we can do to decouple static types and the languages which use them.

Advertisement

The battle between static and dynamic typing enthusiasts rages on and on. Java "upped the ante" by introducing generics and increasing the amount of verbiage in our source files. The dynamicists have responded with Rails and various Python frameworks, showing the world that you can write powerful web applications with an extremely small number of keystrokes. Personally, I'm more in the dynamicists camp, but I do think that there are some problems in the dynamic language world and some of them are political.

Let's play out a scenario. You've just graduated from school. You and your best friend look at the landscape and you figure "hey, we're young, let's make a go at this Web 2.0 thing." You sell your bicycle and some aluminum scrap that you find on the side of the road and you have your seed money. You buy a server, a development machine and download Rails. You and your friend code something up and go live in a week. It's flimsy but it works. You have your first users. Now, you're the only programmer. Your friend is sleeping at the door with a shotgun to keep venture capitalists away from you. Your user community grows, and your first big shopper comes by.. a big public company, with people in suits. They want to buy you.. they start doing their diligence.. they walk in and they are amazed that you are running a 50,000 user system on a simple server using a dynamic language, and it's handling real money. Their technical guru turns around and says "We can't buy this! It could have all sorts of errors in it and we'd never know!" You point to your tests, but he's adamant. "They're hackers!!" he whispers, "you can't buy this!"

Far fetched? Probably. Dynamically typed languages are growing in acceptance, but there are many cautious people in this post-Sarbanes Oxley legislation world.

Now, let's put aside the static versus dynamic language debate.. I think everyone on both sides of it will admit that dynamic languages offer some advantages during development. And, most people would agree that there are some times when it would be nice to check some things at compile time. Can we have the best of both worlds?

It turns out we can. Some languages allow optional type declarations. Visual Basic is the most prominent example of this (not that I've seen anyone work with it this way), but you can type a variable as an Object and send it any message. At runtime a lookup function is called it will attempt to resolve the message against the object. If you like static typing, you simply use types when you declare your variables: ''Dim count as Integer''

The interesting thing is, this feature can move us to a different style of development: write your program using dynamic typing, and when things are stable, fill in the types afterward; I imagine it would be like doing a Sudoko puzzle.

But.. there is a problem.. once you fill in the types, they are everywhere. They annotate each variable declaration, all your method arguments.. changing them is a pain. They nail down your program. It's hard to refactor, it's hard to get tests in after the fact. Your program and its types are snarled together.

Let's go back to first principles.. software design principles and apply them to directly to language design. We should consider the idea that static typing, as we currently do it, may be bad coupling. When we litter a program with type annotations, we're tightly binding an error detection scheme to the form of the program. We can't work with the form of the program independently of that error detection scheme. What if we tried to decouple them? What would we end up with?

I have in mind exactly the types that I would like to use for each of the variables here, but I hate the idea of putting them in the source. What would happen if I could put them in a separate file like this?

Yes, it's ugly. We're repeating outselves. But here's the trick. We don't have to specify everything. Note, for instance, that I didn't give a type declaration for the last method in the class: scaled_rand. It's all optional.

Think it's still bad? Okay, here's the next piece. We can let our IDEs manage things for us. Our IDE can read both files and show us a view where we see the types in the source (if they are present), and we can toggle off their display when we don't want to see them. The compiler will check all of the types for us, but they are independent of the source. We can delete all the types for a particular class if we want to, rename them.. alias them, etc. We can also take the code for the Word class above and give it another set of type signatures in a different program -- we can reuse the form with different types.

I suspect someone has thought of this before, but I haven't researched it. I think it might be a workable way to progressively add static typing to a program on an as-needed basis. What do you think?

I was doing a bit of research to compose a reply to Michael's post. I remembered Gosling had said something about safety being freedom. So I searched Google for it and one of the hits took me to this page:

What does that mean in relation to decoupling types and programming text? It seems that we could write a program in a dynamic language and end up with it completely static, having all of the benefits of static typing (plus a few others) while keeping the program text free of type annotations.

> <p>Think it's still bad? Okay, here's the next piece. We> can let our IDEs manage things for us. Our IDE can read> both files and show us a view where we see the types in> the source (if they are present), and we can toggle them> out when we don't want to see them. Better, we can delete> all the types for a particular class if we want to, rename> them.. alias them, etc. We can also take the code for the> Word class above and give it another set of type> signatures in a different program -- we can reuse the form> with different types.</p>> I am suspicious that optional typing would be useful. The trouble is that there are two kinds of freedoms provided by these two kinds of languages, and you can't mix one with the other.

Static typing gives you Gosling's "safety is freedom" kind of freedom (see my previous message in this forum). It allows optimizers to do a better job, potentially letting your program run faster. It allows static analyzers to do a better job, helping you find and prevent certain kinds of bugs, helping your IDE perform error-free refactors, and so on.

Dynamic typing gives you a lot of freedom to make changes to classes and objects at runtime. If you add a bunch of methods to an object at runtime in Ruby, say an ActiveRecord from Rails, exactly how would you go back and add a type to it? In Java, it would be a subclass of ActiveRecord, and all the calls into it would be checked. To be able to add type information later to a Ruby program, you'd have to write it like a Java program. That to me makes little sense, because you would not be getting the best of either approach.

A more practical in-between approach might be a static language that does type inference. This allows you to not have to type or look at all the types everywhere, though if you were confused, your IDE could certainly pop up a type for it. Because the language would need to define a type everywhere with no ambiguity. Scala is I think an example of this. It's syntax is quite concise, in part because of type inference, but also because it gives a lot of shorthand ways to get at commonly used functionality. But it is statically typed so I don't expect you'll be able to do the kind of object morphing in Scala that you can do in Ruby. So it isn't really in-between, it is just a more user-friendly static language. I'm still learning about Scala, which is here:

> What does that mean in relation to decoupling types and> programming text? It seems that we could write a> program in a dynamic language and end up with it> completely static, having all of the benefits of static> typing (plus a few others) while keeping the program text> free of type annotations.

Gosling's post had nothing per se to say about decoupling types, it was just something I was looking up when posting my actual reply, in which I suggest that to "write a program in a dynamic language and end up with it completely static," you'd have to write it in a very static way, which would mean you'd miss out on much of the stuff that makes the dynamic language compelling. I just thought it was funny that when I was looking at a post about James Gosling's "Safety is Freedom" idea I got an exception from a JVM. That guy's post is pro-dynamic, so maybe he did it on purpose to make a point. But probably not.

Specifying types is an inherent tradeoff between error checking and flexibility. I would argue that for Web apps, especially with the startup scenario that you mentioned, flexibility is much more important.

It seems to me that static typing is more appropriate for traditional software that gets shrink wrapped or placed in a satellite that goes into space. There correctness far outweighs flexibility. The same is probably also true if you are a library developer, where you want your API to be very stable.

Incidentally, another language that seems to have optional typing is Dylan. By default, you get the dynamic typing of Lisp, but you can specify types to constrain things or to assist the compiler in generating efficient code.

> Gosling's post had nothing per se to say about decoupling> types, it was just something I was looking up when posting> my actual reply, in which I suggest that to "write a> program in a dynamic language and end up with it> completely static," you'd have to write it in a very> static way, which would mean you'd miss out on much of the> stuff that makes the dynamic language compelling. I just> thought it was funny that when I was looking at a post> about James Gosling's "Safety is Freedom" idea I got an> exception from a JVM. That guy's post is pro-dynamic, so> maybe he did it on purpose to make a point. But probably> not.

Yeah, that is funny. :)

Re writing your program in a static way, though, I wonder.. There would be a hole in this system with regard to creating type on the fly, etc., but I think you can take most programs that don't use those features and make them statically typed if you, for instance, could introduce an interface for each method and then glom them together to make union types for whatever methods a particular calling context needs. Maybe not. It would be interesting to try.

> I am suspicious that optional typing would be useful. The> trouble is that there are two kinds of freedoms provided> by these two kinds of languages, and you can't mix one> with the other.> > Static typing gives you Gosling's "safety is freedom" kind> of freedom (see my previous message in this forum). It> allows optimizers to do a better job, potentially letting> your program run faster. It allows static analyzers to do> a better job, helping you find and prevent certain kinds> of bugs, helping your IDE perform error-free refactors,> and so on.

Yes, but if I remember correctly, the developers of C-omega and VB9 were looking at stronger roles for dynamic typing, and all commonly used static typing schemes do have holes in them.

In any case, I think I made a mistake in framing this idea in the context of dynamic vs. static. The thing I was proposing could also be used in program where static types are mandatory past a certain point in development. It could be a compiler switch.

The thing we could get if we decouple types from program text is a degree of freedom that we don't have when they are mixed in the language. Like the example that I gave above, you could have code for a class in one file and use two different type annotation files for it in two different programs. I suspect an IDE could add the construction of these files by attempting to infer types. In cases where they couldn't, they could query us to select from a set of candidate types or introduce another.

Optional typing is in plenty of languages, Haskell, Clean, ML, Scala, Fortress, etc. The inventors of these languages, user guides, etc. normally advice you to declare the types for function arguments, but not to bother for local variables. The reason for declaring types for functions is that the types are a nice peice of information for someone wanting to use the function and it enables overloading.

For example in a numerical API an add method is ambiguous for:

d = 1.5
i = 2
i.add( d )

Does the above produce: 3.5 or 3 or Error. With typing you can distinguish cleanly between the cases and allow the user of add to select which one they want.

3. I find a strong correlation between: wanting to use a scripting language and:

A. Not wanting to test the program

B. Not wanting to write documention

C. Using every feature of the language in every program (to the point where simple features are shunned and something that saves a line but is hardly used by other programmers is favoured). Think: APL one line challenges.

This is possibly why scripting languages have a bad reputation.

4. Initial write speed is far from the dominant cost of a program. Far longer is spent maintaining that program and the advantages of no type declarations in that case a far from proven (in my experiance).

5. The error messages without typing information can be very obscure and can be a long way from the problem, e.g. C++ templates.

6. Python is thinking of adding type information, see Guido's latest few post on Artima, e.g.:

> Optional typing is in plenty of languages, Haskell, Clean,> ML, Scala, Fortress, etc. The inventors of these> languages, user guides, etc. normally advice you to> declare the types for function arguments, but not to> bother for local variables. The reason for declaring types> for functions is that the types are a nice peice of> information for someone wanting to use the function and it> enables overloading.>I don't know much about the languages you listed, though I have been reading about Scala this week, and I don't see that it has optional typing. As far as I can see, what's optional is whether you need to explicitly specify the type. If the compiler can infer it from context, then you don't have to specify.

What I don't understand about how optional typing would work is if the types aren't everywhere, then exactly what do they mean? For example, if I must declare types for function args, but need not for local variables, does that mean I can't use a local variable as a parameter in a call to a function? Because the local variable doesn't have a type, but the function arg does. That seems draconian, but if you say you can use a local variable to pass a value to a function, then that means the value need not be the specified type. Which means the type means nothing I can count on. How does that work?

Oh wait, does it mean that at runtime I check the type? Ah. That might make sense. Is that what they do?

> But.. there is a problem.. once you fill in the types,> they are everywhere. They annotate each variable> declaration, all your method arguments.. changing them is> a pain. They nail down your program. It's hard to> refactor, it's hard to get tests in after the fact. Your> program and its types are snarled together.

This is mostly a solved problem in languages such as Haskell that have powerful type systems featuring type inference. For example, here are two small programs I wrote recently:

All types were checked at compile time, and yet I didn't have to pay an annotation tax.

In contrast, Java-style type checking is confining and syntactically expensive -- the worst-case scenario. For this reason, it's probably best not to infer too much about the general merits of compile-time type checking from Java and its ilk.

> > Optional typing is in plenty of languages, Haskell,> Clean,> > ML, Scala, Fortress, etc. The inventors of these> > languages, user guides, etc. normally advice you to> > declare the types for function arguments, but not to> > bother for local variables. The reason for declaring> types> > for functions is that the types are a nice peice of> > information for someone wanting to use the function and> it> > enables overloading.> >> I don't know much about the languages you listed, though I> have been reading about Scala this week, and I don't see> that it has optional typing. As far as I can see, what's> optional is whether you need to explicitly specify the> type. If the compiler can infer it from context, then you> don't have to specify.> > What I don't understand about how optional typing would> work is if the types aren't everywhere, then exactly what> do they mean? For example, if I must declare types for> function args, but need not for local variables, does that> mean I can't use a local variable as a parameter in a call> to a function? Because the local variable doesn't have a> type, but the function arg does. That seems draconian, but> if you say you can use a local variable to pass a value to> a function, then that means the value need not be the> specified type. Which means the type means nothing I can> count on. How does that work?> > Oh wait, does it mean that at runtime I check the type?> Ah. That might make sense. Is that what they do?

I assume one can refuse optional type declarations completely in the first development phase in favour for freezing some "type profile" after performing unit tests with reasonable code coverage. In a sense a code coverage is some "statement visitor" and you can derive a type profile i.e. function signatures as well as the types of local variables pragmatically. After having derived a type profile from a particular program it can be merged together with an AST of the program and passed to the compiler to generate an optimized version. It would be the exact equivalent of a program with static type annotations but created post hoc. I guess this procedure could avoid Michaels "premature coupling" concern. From each point of development you can proceed with two versions of your program, the type annotated and the type free or dynamically type checked one.

Like others have already pointed out, the way you describe first doing an implementation without type annotations and then specifying a signature for the "public" methods is pretty much exactly how one can work using ML modules (either Standard ML or Ocaml). Things work similarly in Haskell and many other statically typed functional languages. The difference is that types are not optional, but are inferred by the compiler. Working in ML, the compiler would actually give you the precise signature of the implementation that you could then abstract (by removing private implementation details) to a "public" signature.

People, before starting yet another static vs dynamic debate, do yourself a favour and spend one month hacking in ML or Haskell.

> Like others have already pointed out, the way you describe> first doing an implementation without type annotations and> then specifying a signature for the "public" methods is> pretty much exactly how one can work using ML modules> (either Standard ML or Ocaml). Things work similarly in> Haskell and many other statically typed functional> languages. The difference is that types are not optional,> but are inferred by the compiler. Working in ML, the> compiler would actually give you the precise signature of> the implementation that you could then abstract (by> removing private implementation details) to a "public"> signature.> > People, before starting yet another static vs dynamic> debate, do yourself a favour and spend one month hacking> in ML or Haskell.

I like type inferencing languages, but languages have to be constrained in a particular way if you want to use it pervasively. You can have local type inference in a language like Scala (C# and C++ are looking at doing some of that too), but if you want global inference, I think that the state of the art is that your language ends up looking like ML or Haskell by necessity. It just doesn't fly well with subtype polymorphism.

Also, I'm not sure why I'd ever not want to see the type info in code I'm reading? I can understand not wanting to type it all, but looking at variables and not knowing their types never strikes me as useful.