Duck Typing in the JVM
There's a thought-provoking post about invokedynamic and duck typing here: http://headius.blogspot.com/2007/01/invokedynamic-actually-useful.html
Charles asks, "Would all methods accept Object and return Object? Is that useful?"
I would answer that this amounts to a limited vision of duck-typing. Most dynamic languages (and the JVM) allow, as an option, for variables and fields to be strongly typed. In many cases it is profitable to customize generated code to take advantage (when possible) of declared types. In the JVM it is profitable to customize the signature of a method when that methods argument or return types (but not exceptions) are strongly typed at the source level. The best vision for duck typing is to adapt between separately compiled callers via permissive adapters, while allowing implementations to mention strong types (and exploit them locally) wherever the programmer wants. Duck typing should not limit the "ducks" to quack only in a limited range of ways.
The key challenges for the implementor of a byte-compiled dynamic language (IMO) are three: 1. Customizing generated (bytecoded) methods to full range of JVM-capable signatures for methods. 2. Generating the various adapters needed to make the semantically null conversions (Integer/int, String/Object, etc.) and any implicit conversions the language may define. 3. Arranging calling sequences so that, when the caller and callee agree on a concrete JVM signature, the call has a "fast path" which is as direct as possible. I think some sort of "dynamic invoke" and class extension help most with steps 3 and 2, respectively.
Getting the performance right, in this model, also ensures that calls to and from Java will run fast. This is important because Java is the "system implementation language" of the JVM. Every seasoned dynamic language programmer knows how important an efficient "foreign function interface" is. The JVM promises to supply the very best "FFI", if language implementors choose to take advantage of it.
Note my bias against using the VM's built-iin "dynamic invoke" capability to handle argument conversion. Many languages have complex implicit argument conversions which are beyond any reasonable purview of the VM; given the need to handle these, the implementor may as well handle autoboxing there also. I don't seen an argument, yet, for signature conversion by the VM. Excessive adapter proliferation could support such an argument, but it's not yet proven to happen.
By the way, the JVM's way of saying "not strongly typed" is java/lang/Object, with primitives handled by autoboxing. Also, as Bill Joy pointed out ten years ago, you could also build a weak-typing scheme on top of a fictitious interface, allowing a weak type system with several interchangeable kinds—e.g., multiple co-existing languages.
To elaborate on step 3 above, the challenge is to support fast paths without sacrificing generality. Generality requires that if an unexpected argument appears, the runtime system gets a chance to fix up the discrepancy between caller and callee. Generality also requires that the behavior at the call site can change over time. New classes can appear or new behaviors added to existing classes. These requirements can be gracefully met without compromising performance in the common case, where the caller and callee know something about each other, and can successfully predict an efficient calling sequence at compile time. For a concrete example of this, suppose I'm compiling a call to s.indexOf(i), and I have reason to believe (or assume) that s is a java/lang/String and i is an int. I should compile a call site which rechecks the type of s and i (if I don't have a previous assurance) and calls ((String)s).indexOf((int)i). The call site may also need a "slow path" which calls something like Runtime.invoke((Object)s, (Object)i), or perhaps it can use an optimistic deoptimization scheme like the hotspot compilers. It hardly matters, if the fast path is all that gets executed... and that is the common case, in a nicely balanced system.