David Thomas On The Benefits Of Dynamic Typing

In a post to news:comp.lang.ruby, DavidThomas gave this summary:

"Jish Karoshi" <karoshijish@hotmail.com> writes:

So is it true that in this system the first thing that every function ought to do, in order to be robust, is check to see that certain operations have been implemented by the arguments that are passed to it? Is the definition of a type in ruby or a language like it the set of all combinations of operations which an object has implemented? Also, since the return types of functions are not defined, how does function A know that checking for the existence of function B in some parameter is enough to do something useful with the parameter? Do I really need to check the type of every argument passed to my method and then check the return type of every method I call from those arguments?

Possibly the issue is broader than this.

First look at popular languages such as Java, where type-safety is largely defined in terms of interfaces. If an object implements an interface, then it is an acceptable parameter for a method that required that interface, or for assignment to a variable of that type.

What has this gained you?

Well, you know that you can call the methods in that interface on the passed object, and you will not get a runtime failure because they don't exist.

Anything else? Not really, because the specification of type by interface alone totally ignores semantics: I could define

	public class Dave implements Stack {
		public void push(Pushable o) {
			System.out.println("Refusing to push: I'm tired");
		public Pushable pop() {
			return new PushableInt(99);
Type safe, but semantic gibberish.

Now, turn the question around. What do you gain by abandoning this limited form of safety?

Well, we gain immense flexibility. Refactoring Smalltalk and Ruby is trivial compared to (say) Java. Things just move around. There's no need to jump through hoops to satisfy the compiler.

We gain substantial testability. You can construct mock objects very, very easily, and test out code with far less overhead. You can test objects before the classes they rely on are finished (or even started). This ability to test partial classes also lends itself to easier incremental testing.

We gain expressiveness. I don't know how to describe this one objectively: I just know that I find this kind of code speaks to me more directly: I'm dealing with objects, not object categories.

This is not a thing that can be argued rationally. I was a strong-typing advocate for years, and was nervous when I used languages such as Smalltalk and Ruby. However, I now find Java a very frustrating language to use, and find myself writing higher-quality code in Ruby. In the end, the only way to find out is to try it for yourself and see. Write some Ruby code, and wait until you experience that a-ha! moment. Then write some more code until you start developing an idiomatic style. Get comfortable with RubyTestUnit. Then take on a largish project, and see what you think.



Dave, Could one not write trivial (and quite frankly stupid) examples of how dynamic types systems fail to catch errors until it is too late (runtime)? Could one not write trivial (and quite frankly stupid) examples of how unit tests can pass when the programmer wants failure? Why go down this road? If you want to show something please back it up with something more substantial than the nonsense provided above. Better yet, lets work toward improving software development without shooting down things that we really don't understand and don't wish to spend time studying.

{Indeed, one could also write similar stupid examples about how dynamic type systems fail.. Dave is using similar arguments that doctors used to use when they did not sterlize their utensils. What does sterilizing your utensil really gain you? Only inexperienced amateur doctors would sterilize their utensils back in the day - the professional doctors performed better surgeries without sterilization. At least, until they started thinking rationally, that is - now all utensils are boiled in hospital basements at high temperatures and no doctor uses unsterile utensils. The idea that our code should be an unpredictable unsterile mess without restrictions or discipline is nonsense. I consider dynamic typing to be a lack of typing - it's brushing typing under the rug as if it doesn't really exist and things just magically change types without you really knowing it. Dynamic typing along with weak typing reminds me of a word: maybe. It should be called "MaybeTyping". What is that? is it a bird, an airplane, or is it superman? Maybe! I'm not really sure! Is it an integer? My type system says "maybe!". If you told a mathematician that his x and y coordinates just changed magically into strings and are no longer integers, he'd be like "is that a bug? who did that to us?"}

Dave -- thanks. I've been reading a lot about BenefitsOfDynamicTyping as part of trying to learn PythonLanguage. And these comments really made sense to me. -- MichaelChermside

The reason this debate exists is that languages do not give you a choice. Either they require static types everywhere or don't support them at all. In a language that supported static typing but made it optional, you could leave out types when writing new, informal code, but declare types for standard interfaces that are widely used. Would that give you the best of both worlds. -- BrianSlesinsky

(Or it would give you the worst of both worlds. Has the experiment been done?)

DylanLanguage seems to have this feature.

In fact, VisualBasic gives you pretty much the same thing - it has static typing, and it has the VARIANT type that can be transparently converted to any other type. The use of VARIANT type is now strongly discouraged by MicroSoft, for performance and robustness reasons. -- DmitryJemerov

CommonLisp does it too. Everything is dynamically typed, but you can declare the types of some things and the compiler (if you have a good compiler) will infer the types of other things; operations on objects of known type can be implemented more efficiently. The existence of type inference distinguishes this from VB: you can get efficient operations on things whose types haven't been declared explicitly. -- GarethMcCaughan

ObjectiveCee also gives you a choice about dynamic typing or static typing for object types. It also lets you override the equivalent of "doesNotUnderstand" so you can do message-redirecting.

There was a dialect of SmalltalkLanguage called "StrongTalk" some years ago that did some of this. There have also been a number of commenting conventions in Smalltalk that allow scaffolding to provide hints about possible type-safety issues (which is mostly of what you get from a "Strong" type system anyway, if it allows cast operators). These end up providing an EiffelLanguage-style weak typing system that complains if the compiler can definitely show that your code can't work, but allows ambivalent (to the compiler) operations. Such systems catch most of the typos (for example, a misspelling that invokes a method that still exists, but does something totally unintended), which are a major source of Smalltalk runtime faults. -- TomStambaugh

SoftTyping implements this idea.

No, SoftTyping is not quite the same, but in my opinion even better. At least in CommonLisp, StrongTalk and ObjectiveCee you have the choice to declare elements with or without types. Typed elements are statically checked and untyped elements are not. If you want to have static type safety for an untyped element, you have to change it manually to a typed element. With SoftTyping, you don't have to do that because it relies on TypeInference, so it's presumably much more powerful. The essence here is that SoftTyping does not reject a program if it's not (provably) typesafe but asks the programmer if it should reject or accept it. (I don't have enough experience with VisualBasic and DylanLanguage, so I don't know what kind(s) of typing they provide.) -- PascalCostanza

I can't believe we're still debating either-or in an emphatically both-and world. I wrote a whole rant on this topic in the context of web apps. See http://virtualschool.edu/wap under "The Problem" in the left column. -- BradCox

HonestProgrammers? don't write deliberately stupid classes like the one you give above, Dave. They try to implement correct semantics when they implement an interface. For them, static typing tells them when they have done something stupid. Whenever I use a dynamically typed language, I (a) know what types things are supposed to be, and (b) am terrified that I have made a mistake. I really do like PythonLanguage, but when I am working in a team I like to have the added overhead of static typing to know that other programmers have at least thought about what they are passing me. -- JohnFarrell

static typing == time overhead (programmer), but dynamic typing == USER overhead.

feel the difference, John.

-- AndreySidorenko

I think Dave's point is that a static type system does not help HonestProgrammers? avoid doing something stupid when implementing interfaces. In my experience, most of the bugs in my code are logic errors. That is, errors caused by me not implementing the semantics of an interface correctly. The type system does not catch these errors for me at compile time, despite all the extra fluff I have to write to tell the compiler which interfaces my classes implement. In fact, I have to write lots of UnitTests to ensure I have no errors, just like I do in dynamically typed languages like RubyLanguage or PythonLanguage. Because I am using polymorphism, the strong typing is not really giving me an advantage and it is also breaking my flow of creativity and enforcing artificial constraints on the object collaborations I can create. -- NatPryce

I think the important point in DavidThomas' example is not that it's deliberately stupid code, but that StaticTyping doesn't buy you much. However, a negative effect of StaticTyping is that it possibly disrupts your StreamOfConsciousness. Here is an example: suppose you want to write a class that needs to implement an interface with several methods. Here is the interface:

	public interface AnInterface? {
		public void open(...);
		public int read(...);
		public void close(...);
Now assume that you want to start to implement the open method and that this is a very complex task in your particular case. JavaLanguage (and other like-minded languages) forces you to provide something for the other two methods. In order to start with the task you're after as soon as possible you DoTheSimplestThingThatCouldPossiblyWork:

	class MyClass implements AnInterface? {

public void open(...) { ... }

public int read(...) { // do nothing }

public void close(...) { // do nothing } }
Now we have some problems:

	class MyClass implements AnInterface? {

public int read(...) { return 0; }

... }
	class MyClass implements AnInterface? {

public void open(...) { ... }

public int read(...) { throw new RuntimeException(); }

public void close(...) { throw new RuntimeException(); } }
	class MyClass implements AnInterface? {

public void open(...) { ... }

public int read(...) { throw new RuntimeException("MyClass::read not understood/implemented"); }

public int close(...) { throw new RuntimeException("MyClass::close not understood/implemented"); } }
I hope this gives you the punchline because this is exactly the default behavior of dynamically typed languages. So obviously, one of the main advantages of DynamicTyping is that your StreamOfConsciousness is not unnecessarily disrupted by useless compiler errors at exploration time that you need to fix somehow in order to be able to continue your work. On the contrary, a language with ManifestTyping might even be more unsafe in that it allows you to do away with compiler errors in a too ad-hoc manner, as shown above. (However, the last part is just speculation on my side, I don't have evidence for this.) -- PascalCostanza

The punchline feels very weak to me, because I don't have to implement the interface to test the class. I can write my tests to MyClass just fine. I only have to implement the interface when I get to integration tests where I have to pass it to something else. At that point I need all three methods whether my language is static or dynamic, since the thing I'm passing it to needs them. And I'd rather it said it needed them in a way the compiler understands, instead of in a comment. -- ScottMcMurray

[See also: StaticTypingHindersRefactoring]

However, the other side of the coin is that StaticTyping buys you some advantages as soon as your code is in a nearly finished state. (That is, when exploration has more or less come to an end and you are in the stage of polishing your code.) Here is a quote by GuySteele:

In the end, I have to say that the type checking was more help than hindrance, [...]. I had the same experience with Haskell that I had twenty years ago with ECL (which was, in effect, also a strongly-typed dialect of Lisp): almost always, once I made the type checker happy, the program was correct. (in "Building Interpreters by Composing Monads", POPL '94)

Another, more concrete example of this can be found in http://perl.plover.com/yak/typing/typing.html, especially in http://perl.plover.com/yak/typing/samples/slide027.html where MarkJasonDominus shows an example how the MlLanguage type checker finds an infinite loop bug.

So, in conclusion, what I would like to see is a language that allows me to disable StaticTyping while I am at the exploration stage, and later on enable StaticTyping when I am close to finishing the code in order to get additional advantages. However, this should be a type system with TypeInference because I don't want to change my source code just to get StaticTyping. So basically I would like to have SoftTyping. -- PascalCostanza

Note that neither HaskellLanguage nor MlLanguage support runtime polymorphism. Static typing works great if the programmer cannot implement their own implementations of a predefined type. Once they can do that, existing type systems (even those as expressive and unobtrusive as Haskell's) do not help them catch errors. -- NatPryce

I loathe assumption in my languages. Try VisualBasic without Option Explicit, or reference form names directly as objects. This, coming from a person that actually kinda likes VB. A language that's weakly -and- statically typed is the worst of both worlds, to me. I'll take static-strong or dynamic-strong, thanks. However, I wonder if there's room for a new form of refactoring here. I still get nervous working in a dynamic language, but it's feeling better, and it's definitely worth the learning exercise. -- JeremyDunck

VB without "Option Explicit" is a minority way to handle dynamic declaration. In most dynamic languages you have to at least create a variable first, usually by assignment. VB does not even require assignment. Thus, VB is a poor specimen to judge dynamicy. For example:

  a = b + c
If "c" is not defined in VB without Option Explicit, it would be assumed Nil, which is rarely what you want. However, in most other dynamic languages it would raise an error. But, "a" being a newly encountered variable would not (assuming b and c are valid). I have had very little trouble with "left-side" dynamic declaration, but agree that VB's right-side dynamism stinks to high heck. I wish VB had an Option Left or the like.

Most scripting languages have (mis)features like this. It's part of the idea of a scripting language.

Let's try to define Dave's example in HaskellLanguage:

 class Stack my_stack where
	push :: my_stack a -> a -> my_stack a
	pop :: my_stack a -> a
So far so good, the interface was defined. Now I'll try to make the wrong implementation:

 data Dave a = Dave [a]
 instance Stack Dave where
	push this o = putStr "Refusing to push: I'm tired"
	pop this = 99
Ops! The compiler complains about it. Says the type of push in the implementation (i.e. instance declaration) doesn't match the type given in the interface definition (i.e. class declaration). The actual error given by Hugs is:

 Type error in instance member binding
 *** Term		: push
 *** Type		: Dave a -> a -> IO ()
 *** Does not match : Dave a -> a -> Dave a
After fixing it the compiler now complains about pop:

 Cannot justify constraints in instance member binding
 *** Expression	: pop
 *** Type	  : Stack Dave => Dave a -> a
 *** Given context : Stack Dave
 *** Constraints	: Num a
It says that pop is less generic than it should be.

While we could do some tricks to write an unconforming but type-checked implementation (e.g. returning this for push) it's less simple than in JavaLanguage because HaskellLanguage's type-system is much more expressive. We could make the cheating much harder in Haskell by encoding the semantics in the types (i.e. simulating DependentTypes with rank-2 polymorphism).

There are fewer BenefitsOfDynamicTyping when you compare them with languages with advanced StaticTyping.

Oh, really powerful static type systems with inferencing and stuff are great (but bear in mind Nat's comments about runtime polymorphism). Unfortunately, that's not the kind that the overwhelmingly vast majority of working programmers are allowed to use. Maybe sometime around about JavaTwelvty (s/Java/DotNet/ if you wish) the claim made by some (not necessarily anyone writing on this page) that working programmers shouldn't want dynamic typing more than the weak flavour of static typing they're allowed to use won't look quite so ridiculous.

Hmm, I fail to see how is "runtime polymorphism" (i.e. subtyping IIUC) impossible to mix with the technique I described. Need you to write interfaces (with optional implementations)? Ok, write down type-classes. Need to do some code inheritance to your data-structures? Ok, write the instance declarations. Need to write heterogenous collections of data handling the same interface? Ok, use ExistentialTypes? (which can be automatically derived from your class declarations). Seriously, modern static-typing systems are very powerful and almost invisible, learning HaskellLanguage or CleanLanguage is as enlightening as learning SchemeLanguage or SmallTalk. NatPryce comment is factually incorrect.
Static Type Inference systems like Haskell's are less onerous to work with than Java or C++'s ManifestTyping systems, but they still don't address the real problems. A major point of DynamicTyping is allow the developer to work without interruption. Also, what if you want to write a piece of code like this:

 (defun handle-messages (message-queue channel)  ;<- Is it me, or does Lisp code start becoming beautiful after a while?
	(dolist (msg message-queue)			;Stephenson did call Lisp "The Only Beautiful programming language. :)
	(dispatch msg channel)			;Who's he, got a link?
	(make-log-entry msg)))
 (defmethod dispatch ((msg Special-Message) (chan User-Channel))
 (defmethod dispatch ((msg Special-Message) (chan Computer-Channel))
 (defmethod dispatch ((msg Normal-Message)  (chan User-Channel))
For brevity, just envision there are many types of messages, Special-Message is just one. There could also be many types of channels. Even if they're in a hierarchy, this kind of elegant multi-type construct is very difficult to handle in Haskell's type system because it requires run-time dispatch. You can come slightly closer in ManifestTyping systems like C++ or Java because they allow you to dispatch on methods at run time with constraints. But again, this runs into the problems detailed above.

Of course, that ignores the fact that we can't dispatch on multiple parameters without a complex pattern. One of the reasons that C++ templates are so dang popular is that they let you do this kind of thing (in a limited fashion).

In Ruby or Python or (of course) Lisp, we can express these kinds of algorithms with relative simplicity. That's the advantage, because this kind of code tends to be much clearer, smaller, and easier to maintain.

I fail to see how the HaskellLanguage code is really less elegant than above:

 handleMessages messageQueue channel = mapM_ handle messageQueue
	where handle msg = do dispatch msg channel
				makeLogEntry msg

data Message a = SpecialMessage a | NormalMessage a data Channel a = UserChannel a | ComputerChannel a

dispatch (SpecialMessage msg) (UserChannel chan) = undefined dispatch (SpecialMessage msg) (ComputerChannel chan) = undefined dispatch (NormalMessage msg) (UserChannel chan) = undefined
Is the code really "very difficult to handle in Haskell's type system because it requires run-time dispatch"? The code uses runtime dispatch (i.e. the pattern clauses in the dispatch definitions) and is statically proven to work. Also the compiler will warn us of undefined cases (e.g. NormalMessage/ComputerChannel) that may trigger runtime errors. Using pattern matching works for arbitrary number of parameters and nested patterns, so it can be used to deal with any "kind of elegant multi-type construct".

(Moved discussion on side-effects and Haskell to HaskellExampleForMutabilityOnObjects)

What I dislike about Haskell's approach is that it is very brittle. TypeInference systems allow you to cheerfully paint yourself into a corner without realizing it. Let's say later, you want to allow strings into this dispatch mix as messages. You're going to keep your higher types, but sometimes you just want to write a raw string to channels. Perhaps this is a primitive upon which other features are based. Perhaps it's just convenience.

To do it in Haskell, you now need to add Haskell's string type to the type Data. Now, it's quite possible that logically, this would never break. You as the system designer say, "The string could never get [to a place where it would break]. It's not how the system works. If it does, then I have more serious problems than just a run time error." That's fine, especially during the early phases of a project. But a statically typed system won't allow such scenarios through.

There are indeed fewer advantages of DynamicTyping compared to TypeInference. Fewer doesn't mean "none". Flexibility is a big issue. One of the reasons I like SBCL and CMUCL so much is that early on, you operate without types. Once you get your project to a working state, you add type declarations for optimization, and then the system does TypeInference and lets you know.

This, to me, is the ideal combination. Early on, static typing is bad. We need to use dynamic tools during the dynamic phase of development. Once the project settles down, static typing becomes far more useful because the project is, well, static.

If OCaml allowed you to turn off typing at first and just do dynamic dispatch, I'd use it far more often.

"But a statically typed system won't allow such scenarios through." That's untrue. As I stated above the compiler will warn us of undefined cases, but the code will compile if necessary (it's your own risk and I never do this). Your scenario would just extend the "Message" declaration and the "dispatch" operation:

 data Message a = SpecialMessage a | NormalMessage a | RawMessage String

dispatch (RawMessage str) (ComputerChannel chan) = undefined
The type-system doesn't forbid you from doing that, so your statement is factually incorrect. So far I wrote code equivalent in number of tokens/lines/characters that disproves your initial statement

Equivalent? You have nearly twice as many tokens as the Lisp example, and that's generously not counting | and =. You also have more characters. Even worse, you have a type union statement that a programmer must maintain. Wasn't the point of TypeInference to not declare type junk?

The "data" statement defines a type and the constructors, it isn't "a type union statement". Also they aren't "type junk" or would you call all you "defclass" statements "type junk" in Lisp? In your Lisp pseudo-code you never declared the classes for "Special-Message", "User-Channel" and "Computer-Channel" that would be necessary to create the appropriate objects, so to be fair I didn't count their influence in the final code size (to be correct we should find the size of the final working system).

In Lisp, using defclass creates new types. These types may or may not be aggregate of other types. We never need to declare a type, then declare an aggregate of that type just to get a function to accept it as a parameter. The class definition example was omitted from your example as well as mine. Therefore, it's perfectly fair to count the symbols in your "data" statement. That statement is particularly irritating because it requires programmer maintenance. Every time I need to make "dispatch" accept a new type, not only do I need to write the new definition (which is inescapable), but I also now need to do book-keeping for the compiler.

Also I disproved your statement on how would the type-system behave in design changes. Before changing the subject again and stating incorrect "facts" about such languages please answer my question if the HaskellLanguage code is really convoluted, ugly, more complex, whatever than your original example.

Such questions are matters of opinion. I think that it looks messy and confusing. The syntax is complicated compared to Lisp (not fair though, lisp has almost no syntax) or an equivalent Ruby program.

The diversion was simply to remind me why I don't like Haskell. For that I apologize. You must forgive me if I'm a bit confused about Haskell. Since HUGS won't run on any of my machines, I can't play with it as much. It's been about 3 years since I did anything serious with it. Besides, what you're saying isn't really true. You still had to add the string to that type union. In a system based on DynamicTyping, you just would pass it in. Heck, in Lisp you'd probably just tap out a line of lisp code on the repl and watch the results. When it breaks, you add the appropriate CLOS dispatch statement and do it again.

You said "But a statically typed system won't allow such scenarios through." which is untrue.

Let's assume for a moment that you'd want to program a large system in Haskell. When you're prototyping these systems, you'd constantly be pulling tricks like what you describe. In the end, all it really is DynamicTyping through circumvention. If you're deliberately circumventing the type system, what good is it to even have it there at all besides optimization? What, at the moment that you perform such an action, does it buy you? As with DaveThomas's elaborate example, when you start making hacks like this, you begin to emulate DynamicTyping in StaticTyping, with ungainly workarounds.

Huh?!? Please explain me how is the Haskell code more convoluted or complicated than the CLOS example? What are the tricks that I pulled off? How is this emulating DynamicTyping in StaticTyping?

You need extra symbols to keep the inferencer happy. You're creating "data" as a classification of types suitable for the first parameter of the dispatch method. Could you get rid of that statement, or have it omit a type, and not be stopped by your type checker? Further, I'm not sure you really did what I said you should do in that example. You added a "RawMessage?". I said I would add a built-in string class. Now, since we've established that my Haskell is toxically rusty, this is mere speculation: You'd had to declare RawMessage? to be a string. Why exactly did you do it this way? Why not just say "data = ... | String"? (don't post on the wiki before your first cup of coffee, that's my lesson of the day).

Is that a sufficient and factual answer? I think if StaticTyping was going to solve anything, it would have begun to show by now. What exactly has ManifestTyping and TypeInference changed in the programming world? Not much. On the other hand, DynamicTyping only becomes more popular over time, and we hear all kinds of very intelligent professionals say, "DynamicTyping is where it's at!" Why do you suppose that is?

As those are fallacies there's no need to answer them. Look I'm trying to refute your statements and you keep changing them, first was saying that Haskell couldn't do runtime dispatch, then you said that we couldn't change our code to deal with raw strings. What will be next? You are stating several different ideas here and I'm dealing with some of them, but if you keep stating new things without stopping to acknowledge that some of them were misconceptions than we will go nowhere.

[I think he's simply stating that dynamic typing is better than static typing for a number of reasons, he's not changing his statement, he's elaborating on it. He's also correct that dynamic typing is more powerful, statically typed programs are a subset of dynamically typed programs. {Not true. In Haskell you can overload on the return type. You might be surprised how useful that can be.} Dynamic typing is also more flexible, you can program while working one the problem, rather than programming to please the compiler. {I don't mind arguing with the compiler when the compiler's right. Having code work correctly the first time it compiles is addictive.} His final statement is also correct, many of the top professionals are starting to prefer dynamic type systems now that computers are fast enough to make them feasible. Static type systems only advantage over dynamic systems are speed, and some assistance from making typo's.]

And automated refactoring support; i.e., the renaming half of a unhappy accident problem. I can sling code all over the place, and run the tests once when I'm done. It's exceeding rare that they break. There are some cases with dynamic code which simply can't be safely refactored. Static typing lets me work faster :) I concede that you do need tool support to gain that though. But all the boilerplate code can and should be generated automatically, which brings me to another benefit: how easy is it to find all the classes which might throw a DoesNotUnderstand? It's trivial with static typing and an editor: just search for "throw new DoesNotUnderstand()" (the stack trace has enough context to determine which method wasn't understood). This is not to say that dynamic typing is tremendously useful; it is, in several circumstances. You simply give up possibilities if you go that route (and vice versa). -- WilliamUnderwood

[There are certainly scenarios where static typing has benefits, such as refactoring, but I'm not sure it's worth the high cost you pay in expressibility and the high overhead in all that extra code to please the compiler. Clearly the perfect language would be dynamically typed, with optional static typing, possibly even a switch to enforce it. Why not have the best of both worlds. I work in Csharp most of the time, but I'll often prototype in javascript, then translate to csharp once I have the design worked out, it'd be nice to not have to do that.]

Well, I am going to (as you might expect) point to Lisp here, because Lisp is one of the few Dynamic languages to do this right. The way for DynamicTyping environments to compete with StaticTyping environments with things like IntelliSense is to write the editor in the language, and then link in. LispWorks does this, although they don't take it to the logical extreme that they could. You can take a running system and literally insert the development environment, diagnose what's wrong, then unlink the functions and have them garbage collected. This allows all sorts of stuff. Static analysis of code (to identify functions, object slots, and macros) is already done even in the SLIME environment for emacs.
Of course, "could" isn't "doing". Currently no lisp editors go as far as IntelliSense IDEs, and I agree the features that, say, Visual C++ has are pretty awesome.
But your final question, "who might throw DoesNotUnderstand?" Why can you use text search and we can't? DynamicTyping doesn't mean WeakTyping. The types are all there, and they do get stated upon creation. In Lisp, Ruby, Python and Perl, this kind of check is rational (although you need smarter checks for Perl).

How do you search for the absence of a method which should be defined? If it's thrown implicitly, there's nothing to search for. Of course you can search for throws DoesNotUnderstand. The point is that in order for that search to find the same things, you need the same boilerplate code. -- cwillu

This assumes that you're using the "message-passing". Even then I'm not sure what you mean. I think you're suggesting when you say "search" that your IDE will look at the class definitions of your currently typed variable and ensure that you are calling a defined method. There are two ways to handle this in the world of DynamicTyping. TestFirst is the most popular. You should always be doing this anyways, since the tougher errors to catch are type errors. Second is checking your debug traces. A friend of mine had an even more aggressive system for perl where it ensured that in a given sub, usage was consistent to some known type. It was a pretty cool emacs extension.
Either way, the IntelliSense features are neat. But, I consider the cost of being able to use them to be greater than the benefits of them. This is a matter of opinion. I'd rather just TestFirst, WriteShortMethods?, and use DynamicTyping.

There's an implication here that IntelliSense cannot be used effectively with dynamic languages. Although I have yet to see it implemented well for dynamic languages, I see no reason why it can't be done. True, there may be ambiguities that cannot be resolved by the IDE, but at least it can offer reasonable choices most of the time. If there are two interpretations, display them both, with context info.
Since dynamic typing is really just a trivial static type system where everything has the same type, has anyone tried using a class called "dynamic" or something in C++ that overloads all the necessary operators to make it function as a hash table, integer, string, or function, based on usage? Overloading would let you go through and replace any usage of dynamic with a more precise type as needed (though the dynamic-to-native-class transition wouldn't be trivial). -- ScottMcMurray

Whether it's trivial may depend on the language. Languages or libraries with lots of overloading, such as "+" used for both arithmetic addition and string concatenation, may require extensive rules to "guess" and convert types as needed. I prefer dynamic languages that don't overload. It makes it easier to read the intent of the code in my opinion.
See also: BenefitsOfDynamicTyping

View edit of June 25, 2013 or FindPage with title or text search