Error Messages in Dynamically Typed Languages

From wikipedia:

[Static typing] allows many errors to be caught early in the development cycle.

Statically typed languages have another, maybe even more important, advantage: clearer error messages.

Can you spot the error in this code:

def print_all(books):
  for book in books:
    print_book(books)

In a dynamically typed language you might get an error like Undefined method `title` for Array in file print_book.xyz on line 2, if print_book is implemented like this:

def print_book(book):
  print(book.title)
  print('----------')
  print(book.summary)

A statically typed language would give you a more helpful error message print_book wants a Book but you passed an Array in file print_all.xyz on line 3.

Now suppose that you fixed print_all() but made a mistake in print_book. For example, books don't have summaries but descriptions. So the correct code is:

def print_book(book):
  print(book.title)
  print('----------')
  print(book.description)

In this case the error messages will be similar for statically and dynamically typed languages: "Undefined method `summary` for Book in print_book.xyz on line 4".

So the statically typed language 'knows' where the error is. In the first case it's print_all's fault: it passed the wrong value. In the second case it's print_book's fault. Dynamically typed languages don't know where the error is. They usually give you a stack trace and some information on what went wrong in the deepest call in the trace. This is annoying, especially if you are calling library functions not written by you.

What I would like to know is

- Are there cases where statically typed language are wrong about the location of the error? Does this happen in practice?
- Is it possible to guess where the error is in dynamically typed languages, and how?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Not much more helpful

In this case, the static error message says "must be a Book" and the dynamic error message says "must quack like a Book [e.g. have a title method]". I don't see that one is particularly more helpful than the other, given a decent way to discover what classes implement a 'title' method.

Location of the error

I'm sorry, I wasn't clear enough. The difference between the two error messages is that the message from the statically typed language ("print_book wants a Book but you passed an Array") implies that the problem isn't the code that implements print_book, but the code that's using print_book. Compilers usually add file & line information to help you find the error. The error message would really look like:

print_book wants a Book but you passed an Array in foo.xyz on line 3

And like this if you're using a dynamically typed language:

Undefined method `title` for Array
Stack trace:
  main() in program.xyz line 7
  print_all() in foo.xyz line 3
  print_book() in print_book.xyz line 2

With this information you can't determine whether the error is in main, print_all or print_book. If you have three calls in you stack trace this isn't a big problem, but usually you have many. It gets even worse if you start using objects or first class functions because then the line you're interested in can be in the middle of the stack trace, surrounded by library calls.

Is this explanation better?

Where, not what

The useful thing about static typing here is not the error message text but the line number. In the dynamic language you have to do a stack walk to figure out the problem.

Of course this assumes that the type of the function invoked is known statically to have a Book parameter. If there is significant polymorphism/type inference at work, however, then you can get back to a similar situation in the static case, except at compile time.

static/dynamic Vs explicit/implicit

This is not realy a question of static vs dynamic typing, but of explicit vs implicit type annotation. A static language with type inference might have the same problem as python and an dynamic language with contracts might not have it.

That's of course why people

That's of course why people suggest that inference should be "local" (i.e., not traverse function boundaries). But even with "global" inference, the problem will manifest itself in the type signature of the function, if the problem is internal to the called function, while a mismatch caused by the caller will be a type error.

"Wrong" location

Are there cases where statically typed language are wrong about the location of the error? Does this happen in practice?

Yes, for some value of the word "wrong." The type checker will find an inconsistency and point to a spot where it manifests but the original source of the inconsistency (as viewed by the programmer) can be some distance away. This is particularly noticeable with type inference.

Even without inference, the problem can also occur when you over constrain types. The error your checker finds will be at point of use but really the problem was at point of definition.

Finally, modern static type systems are getting "interesting" and it's often not entirely clear to a newbie what the implications of a fancy type are. The real error might be in misunderstanding the scoping of an existential type, for instance, but the error reported may be far away.