Software Engineering
programming-languages language-features dynamic-typing
Updated Tue, 06 Sep 2022 01:47:53 GMT

Why dynamically typed languages do not let the developer specify the type?


The dynamically typed languages I know never let the developers specify the types of variables, or at least have a very limited support for that.

JavaScript, for example, doesn't provide any mechanism to enforce types of variables when it is convenient to do so. PHP let you specify some types of method arguments, but there is no way to use native types (int, string, etc.) for arguments, and there is no way to enforce types for anything other than arguments.

At the same time, it would be convenient to have a choice to specify in some cases the type of a variable in a dynamically typed language, instead of doing the type check manually.

Why there is such limitation? Is it for technical/performance reasons (I suppose it is in the case of JavaScript), or only for political reasons (which is, I believe, the case of PHP)? Is this a case for other dynamically typed languages that I'm not familiar with?


Hhere's an example for a clarification: let's say we have the following method in plain PHP:

public function CreateProduct($name, $description, $price, $quantity)
{
    // Check the arguments.
    if (!is_string($name)) throw new Exception('The name argument is expected to be a string.');
    if (!is_string($description)) throw new Exception('The description argument is expected to be a string.');
    if (!is_float($price) || is_double($price)) throw new Exception('The price argument is expected to be a float or a double.');
    if (!is_int($quantity)) throw new Exception('The quantity argument is expected to be an integer.');
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');
    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

With some efforts, this can be rewritten as (also see Programming by contracts in PHP):

public function CreateProduct($name, $description, $price, $quantity)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'name' => array('value' => $name, 'type' => VTYPE_STRING),
        'description' => array('value' => $description, 'type' => VTYPE_STRING),
        'price' => array('value' => $price, 'type' => VTYPE_FLOAT_OR_DOUBLE),
        'quantity' => array('value' => $quantity, 'type' => VTYPE_INT)
    ));
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');
    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

But the same method would be written as follows if PHP would optionally accept native types for arguments:

public function CreateProduct(string $name, string $description, double $price, int $quantity)
{
    // Check the arguments.
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');
    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Which one is shorter to write? Which one is easier to read?




Solution

The point of having static typing is the ability to prove statically that your program is correct with regard of types (note: not completely correct in all senses). If you have a static type system throughout, you can detect type errors most of the time.

If you only have partial type information, you can only check the small pieces of a call graph where type info happens to be complete. But you have spent time and effort to specify type information for incomplete parts, where it can't help you but could give a false sense of security.

To express type information, you need a part of language which cannot be excessively simple. Soon you'll find out that info like int is not enough; you'll want something like List<Pair<Int, String>>, then parametric types, etc. It can be confusing enough even in the rather simple case of Java.

Then, you'll need to handle this information during translation phase and execution phase, because it's silly to only check for static errors; the user is going to expect that the type constraints always hold if specified at all. Dynamic languages are not too fast as they are, and such checks will slow the performance down even more. A static language can spend serious effort checking types because it only does that once; a dynamic language can't.

Now imagine adding and maintaining all of this just so that people sometimes optionally used these features, only detecting a small fraction of type errors. I don't think it's worth the effort.

The very point of dynamic languages is to have a very small and very malleable framework, within which you can easily do things that are much more involved when done in a static language: various forms of monkey-patching that are used for metaprogramming, mocking and testing, dynamic replacement of code, etc. Smalltalk and Lisp, both very dynamic, took it to such an extreme as to ship environment images instead of building from source. But when you want to ensure that particular data paths are type-safe, add assertions and write more unit tests.

Update from 2020: Some dynamic languages now support partial typing of sorts. Python allows type hints, to be used by external tools like mypy. TypeScript allows mixing with type-oblivious JavaScript. Still, the points above mostly hold.





Comments (5)

  • +1 – +1, though tests can only show that errors do not happen in certain situations. They're a poor replacement for a proof that (type) errors are impossible. — May 09, 2011 at 13:17  
  • +1 – @Ingo: surely. But dynamic languages are great for tinkering and quick prototyping, where you express relatively simple ideas very fast. If you want bulletproof production code you could turn to a static language afterwards, when you have extracted some stable core components. — May 09, 2011 at 14:29  
  • +1 – @9000, I don't doubt that they are great. Wanted just to point out that writing 3 or 4 lame tests is not, and cannot be ensuring type saftey. — May 09, 2011 at 14:35  
  • +2 – @9000, True, and the bad news is that even then, its practically impossible. Even Haskell or Agda code relies on assumptios, like, for instance, that the library that is used in the runtime is correct. That being said, in a project with some 1000 LOC spread over some dozen source code files, it is so cool when you can change something and you know that the compiler will point at every line where the change has an impact. — May 09, 2011 at 15:20  
  • +4 – Poorly written tests are not a replacement for static type checking: they are inferior. Well written tests are also not a replacement for static type checking: they are superior. — May 09, 2011 at 16:20  


External Links

External links referenced by this document: