I lived long time on static typed language world. All types were explicitly noted on each variables and arguments. Now I have to use some kind of dynamic typing language. (Smalltalk) The problem is there is no type noted on any message argument, and even on documentation. So, how can I know what should be passed to the each argument? Why the people didn't note type on message arguments? Really isn't it need?
Well, type information will be there, but you have to look for it. Firstly, Smalltalk is really intended to be used in the ST development environment, and viewed with one of the many source browsers, so you should not attempt to try and understand the code as you might print-out of a C program. Using the system browser in Squeak, we can navigate to the String class and look at some of the messages you can send to strings, like this one:
You can see it talks about the receiver, which is the thing you send a message to. As we are looking at the string class, this must be of type String. It also talks about an answer which is, if you like, the return type of the message, and tells you that its type is also a String.
For messages that take parameters, like this:
You can see that the parameter has been given a name that indicates what types can be used - in this case a number or a collection like a list or a dictionary.
So type information is available, but it is not checked by the compiler. If you make a type mistake, Smalltalk will put you into the debugger and allow you to fix it. It is therefore common for ST programmers not to worry to much about type, and fix any errors that occur as their code runs through its unit tests. You will really want to use unit tests when programming in Smalltalk, and there is a nice framework there for you to do just that.
It sounds like you're missing the point of dynamic typing.
It's not unusual for functions etc to expect particular argument data types even in dynamic languages. The documentation of that is just that - documentation, not code. Some kind of asserts are often included in the code, however, to ensure that the datatype requirements are met.
However, a common idiom in dynamic languages is "duck typing" - based on the phrase "if it quacks like a duck, it's a duck". What this means is that, if the value passed in supports all the needed operations, you generally shouldn't care what exact type it is.
So, the downside of dynamically typed languages is the lack of automatic static type checking. You still get some type checking at run-time - the interpreter (or JIT compiler) keeps track of the type of each value/object - but if you want strict type-checking you do it manually and at run-time. The upside is more convenience and flexibility - and while you may need some more unit testing to ensure correctness, you should be doing some unit testing anyway.
The type system used by Haskell is an interesting variation - statically typed, but functions are "generic" by default (giving a kind of duck typing), but with a typeclass system that allows genericity to be controlled (you can say "this argument must be of some type that claims to quack like a duck").
The point of this is that there's a "middle ground", where you can allow any type to be passed as an argument that supports all the needed operations by default, but can restrict the range of allowed types further when you need to. The generic-by-default functions in Haskell are meant to be seen as (similar too) using duck-typing in dynamic languages, yet with the option (by specifying typeclass constraints) to restrict that freedom and recover whatever you degree you need of static type-safety.
This also isn't meant to imply that Haskell is unique. I imagine the same ideas apply in ML family languages, though I've never been an expert in those, and I haven't even played with any for a while ATM so no guarantees. In C++, I usually describe C++ templates as a "broken functional metaprogramming sublanguage". A C++ template is quite similar to a Haskell function - in fact it is a compile-time evaluated function that returns a class or function. The (typename) arguments are duck-typed - there's no further direct restriction other than that they are types. Exploiting that flexibly to the maximum is kinda the point of the SFINAE rule. Concepts were proposed (but withdrawn) from C++0x to do a similar job WRT templates to what Haskell typeclasses do for functions - to provide a middle-ground by restricting that flexibility.
Java interfaces are related to Haskell interfaces, but not quite the same thing. In this context, Java isn't the same as Haskell because Java functions aren't generic by default - though Java has had support for generic functions and classes for a while now. I don't know that Java well, so I don't know if there's a means to that "middle ground" or not.
In the comments, "structural typing" has been described as the functional programming equivalent of duck typing, which is true, except that it's more the statically-typed equivalent of duck typing. Take, for example, this Haskell function type...
The input argument can be any list. The obvious "duck typing" part is that it can take lists of any type, but all those types and more could be accepted by a function with the following type constraint...
The structural aspect is that the mylength signature specifies that any valid argument must "quack like a list". This differs from dynamic-programming duck-typing in that the "quacks-like" constraint is explicit - though of course it can be inferred.
What do I mean by "quack like a list"? Well, list-ness in Haskell is basically defined by supporting the operations associated with a list. For example, a list is built using ":" operations (often hidden behind the square-braces sugar), so any list can be deconstructed using pattern-matching on ":".
I didn't mention this before because all it really means is that "does it quack like a duck" is statically checked. Even C does this to a degree. However, it doesn't (in itself) provide that middle ground that I was trying to describe. Though maybe if you replace the C typedef with the D typedef or an Ada subtype (a distinct type rather than an alias) you can get the same middle ground in imperative languages.
|show 13 more comments|
The type information is needed of course. The whole point of writing programs is manipulating data structures. Why some people think that throwing crucial information away is a good idea is beyond me.
Have a look at these signatures:
In (1) there is clarity. You know exactly what parameters you need to call the function with and it is clear what the function returns.
In (2) there is only uncertainty. You have no idea what parameters to use and you don't know what the function returns if something at all. You are effectively forced to use an inefficient trial and error approach to programming. You never reach the point where you know what you are doing because crucial information is intentionally hidden. All that in the name of simplicity and speed of development. To add insult to injury, this is prefered by some people for some strange reasons and called "modern".
Imagine maintaining a project with hundred thousands or even millions of lines of code written in this "style" by multiple programmers...
I don't know why it is called "dynamic" at all. It is like writing a Java program where every parameter, return value and local variable can only be of type object. Type declarations would then indeed become redundant. You could then invent a static method to call a method "dynamically":
Suggested reading: Dynamic languages are static languages