>What exactly does it mean to have "good dynamic typing facilities"?

Picking Common Lisp as an example:

(NOTE: Some of the features are also present in good statically typed languages as well, so what I advocate is to use good, well-featured languages, not really static vs dynamic.)

(NOTE 2: I'm sorry for being such a fanboy, but that thing is addictive like a hard drug...)

0. Code is a first class citizen, and it can be handled just as well as any other type of data. See "macros" below.

1. The system is truly dynamic: Functions can be redefined while the code is running. Objects can change class to a newer version (if you want to), while the code is running.

2. The runtime is very strong with regards to types. It will not allow any type mismatch at all.

3. The error handling system is exemplary: Not only designed to "catch" errors, but also to apply a potential correction and try running the function again. This is known as "condition and restarts", and sadly is not present in many programming languages.

4. The object oriented system (CLOS) allows multiple dispatch. This sometimes allows producing very short, easy to understand code, without having to resort to workarounds. The circle-ellipse problem is solved really easily here. (Note: You can argue that CLOS is in truth a statically typed system, and this is partly true -- the method's class(es) need to be specified statically, but the rest of arguments can be dynamic.)

5. The macro system reduces boilerplate code to exactly zero. And also allows you to reduce the length of your code, or have very explicit (clear to read) code at the high-level. This brings down the level of complexity of your code, and thus makes it easier to manage. It also reduces the need for conventional refactoring, since macros can do much more powerful, automatic, transformations to the existing code.

6. The type system is extensive -- i am not forced to convert 10/3 to 3.333333, because 10/3 can stay as 10/3 (fractional data type). A function that in some cases should return a complex number, will then return the complex number, if that should be the answer, rather than causing an error or (worse) truncating the result to a real. Arbitrarly length numbers are supported, so numbers usually do not overflow or get truncated. (Factorial of 100) / (factorial of 99) gives me the correct result (100), instead of overflowing or losing precision (and thus giving a wrong result).

So you feel safe, because the system will assign your data the data type that suits it the best, and afterwards will not try to attempt any further conversion.

7. The type system is very flexible. For example, i can (optionally) specify that a function's input type shall be an integer between 1 and 10, and the runtime will enforce this restriction.

8. There is an extensive, solid namespace system, so functions and classes are located precisely and names don't conflict with other packages. Symbols can be exported explicitely. This makes managing big codebases much easier, because the frontier between local (private) code versus "code that gets used outside", can be made explicit and enforced.

9. Namespaces for functions and variables (and keywords, and classes) are separate, so they don't clash. Naming things is thus easier; this makes programming a bit more comfortable and code easier to read.

10. Documentation is built into the system - function and class documentation is part of the language standard.

11. Development is interactive. The runtime and compiler is a "living" thing in constant interaction with the user. This allows, for example, for the compiler to immediately tell you where the definition of function "x" is, or which code is using such function. Errors are very explicit and descriptive. Functions are compiled immediately after definition, and it can also be dissasembled (to machine language) with just a commmand.

12. Closures are available. And functions are first-class citizens.

13. Recursion can be used without too many worries -- most implementations allow tail call optimizations.

14. The language can be extended easily; the reader can also be extended if necessary, so new syntaxes can be introduced if you like. Then, they need to be explicitely enabled, of course.

15. There is a clear distinction between "read time", "compile time" and "run time", and you can write code that executes on any of those three times, as you need.

16. Function signatures can be expressed in many ways, including named parameters (which Python also has and IMO is a great way to avoid bugs regarding to wrong parameters / wrong passing order.)