5
$\begingroup$

I program in Python, which is a well-known dynamically typed language. I understand dynamic typing to mean mainly that "operations" (in a loose sense) in the language are either allowed or denied (raise an exception) based on the type of their operands, and that this type-checking is deferred as long as possible. My question is this: if that is what a fair characterization of dynamic languages, what would happen if the run-time type checking was removed?

It seems to me that if you want to make a meaningful distinction between a dynamically typed language like Python and an untyped language, you would have to define what actually would happen in the untyped language in situations in which the type system of a dynamic language would take action. For example, in Python, if I attempt to subtract an Integer from a String, e.g., "spam" - 5, I receive a TypeError. If that's what makes Python dynamically typed, what would an "untyped Python" do in that situation?

$\endgroup$
9
$\begingroup$

Your characterization of dynamically typed languages¹ is broadly correct. However, it is somewhat incomplete. Types go beyond type checking: they also characterize values. In Python, this is a fundamental part of the language.

In Python, every value is an object, and the type of a value is for the most part determined by the methods it supports. This is known as structural typing, and especially when done dynamically as duck typing. The main job of the - operator is to invoke the method __sub__ on an object. It performs a few additional type checks, and you can explore what happens when those type checks are removed by calling __sub__ directly.

>>> (3).__sub__("a")
NotImplemented
>>> "a".__sub__(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__sub__'

The type errors are actually coming from verifications performed by the - operator. When you get down to __sub__, what happens is up to the method provided by the object.

  • The object constructed by the literal 3 is a built-in integer; its __sub__ method performs case analysis on the type of the argument. The innards of that method do something like this:

    if the argument looks like an integer:
        perform integer subtraction
    else if the argument looks like a floating-point number:
        convert the integer to floating point
        perform float subtraction
    …
    else: return NotImplemented
    

    The fallback case is a design choice of the built-in __sub__ method. It doesn't do a dynamic type check with a boolean result, but a dynamic type analysis.

  • The object constructed by the literal "a" is a built-in string; it has no __sub__ method. There is no type check per se here. The runtime engine does check whether there is a __sub__ method, but only as part of locating it: there isn't a check operation that's not intrinsically part of locating the method.

Thus “untyped Python” is a difficult concept to define — you can't take out the types without taking out fundamental parts of the language. You could phrase the question differently: what if, say, the code of the __add__ method of a string ("a".__add__) was applied to the data of a built-in integer?

>>> int.__add__("a",1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor '__add__' requires a 'int' object but received a 'str'

This here is an actual type check. If this type check wasn't performed, what would happen would depend on the implementation. In a typical implementation, the code of the __add__ method for integers would attempt to access the object "a" as if it was an integer. Depending on the exact layout of objects in memory, it might do things like:

  • treat "a" as the integer 97 (because it's reading the same bytes and interpreting them differently);
  • interpret a location in memory as a pointer to an address, and operate on a bunch of bytes that happen to be present at that address, and thus construct a bogus integer as a result;
  • interpret a location in memory as a pointer to an address, and trigger a page fault because this address is not mapped in the calling process, resulting in the process dying of a segmentation fault;

In general, what happens on a typical computer when you remove type checks (static or dynamic) is undefined behavior. Eventually a bunch of bytes is interpreted in a way that's unrelated to the way it was constructed, which can lead to anything: a wrong result (because the bytes happen to mean something), a segmentation fault or other crash (because the bogus data leads to attempting to access a memory address that isn't mapped, or to execute data as code which may contain an invalid instruction), weird things happening (because the bogus data leads down a code path that has nothing to do with what the program should have been doing), etc. In C (a language which has no strong typing, meaning that the programmer can easily lie to the compiler about the type of objects), undefined behavior is a fact of life, and it famously can do anything including making daemons fly out of your nose (most computers fortunately lack the requisite hardware).

¹ Not “dynamic languages”, which I don't recommend using as it's a lot fuzzier.

| cite | improve this answer | |
$\endgroup$
10
$\begingroup$

A language in which automatic type checking is not available, an operation would make assumptions about the types of its operands. On the event that these assumptions are false, the operation, not knowing anything about this, may produce nonsense results.

Assembly is such language. It has, for instance, separate addition operations for integers and floats. If you perform integer addition on floating point values you will get a result which is correct as neither integer nor float. Depending on your definition of correct.

| cite | improve this answer | |
$\endgroup$
2
$\begingroup$

One possibility is that the operation produces nonsense results, as Karolis Juodelė explains so nicely.

Another possibility is that the operation causes memory corruption, undefined behavior, or introduces a security vulnerability. Consider array access, e.g., writing to A[i]. Some languages define a type system to ensure that the index i is in bounds. Some do run-time checking of whether the index is in bounds and throw an exception if it is not. And then there there are others -- like C and C++ -- that do no array bounds checking. If you try to write to A[i] at an index i that is out of bounds, the program goes ahead and writes to some other memory location outside of A. This can cause memory corruption and crashes, but worse, it can introduce security vulnerabilities: an attacker who controls the value of i can exploit the buffer overrun vulnerability, inject malicious code into the program, and take control of the program. That's about the most severe kind of security vulnerability possible, and it is possible because of the lack of any run-time checking.

So, another possible outcome of such a mis-typed operation is that this might introduce security vulnerabilities that could be exploited by an attacker to take control of your program. This is a reasonable argument in favor of some kind of type checking (whether run-time or static) over absence of any type checking.

| cite | improve this answer | |
$\endgroup$
-1
$\begingroup$

The below is the conclusion that I have drawn from answers to the SO question Does “untyped” also mean “dynamically typed” in the academic CS world?.

For a dynamic programming language L the following two statements are equivalent:

  1. L is dynamically typed.
  2. L is untyped.

In other words, there is no meaningful distinction between a dynamically typed language and an untyped language, whenever we speak about dynamic languages like Python, Ruby or JavaScript. These are high-level languages that implement some object model. Objects are the only data entities that can be referenced. There is no notion of pointers to memory chunks, no pointer arithmetic. There are two main kinds of situations that are reported as type error: (1) evaluation request is not understood, (2) the request is understood but rejected because a transition to an illegal state is requested.

A canonic representant of the (1) group is method (or attribute) resolution failure. That is, for an object o, there is a request of method invocation o.m() but no method is associated with o under the name m. (And no interception handler is defined.)

An example for the (2) group is a request to create a class that is a subclass of a non-class object (applies to both Python and Ruby). Or, in Python, to create a class with the metaclass parameter used whose value is a class that is not a descendant of the type class (i.e. whose value is not a metaclass). Or, in Ruby (as of 2016), to create a subclass of the Class class.

In my opinion, only the type enforcements of the (2) group can conceivably be removed. This would result into the possibility to create wild and/or unsupported structures between objects. But in the case of the (1) group, there is no clear candidate of what a "type enforcement removal" should mean. One can only invent some nonsense behavior in response to such situations.


(Added after 2 days.)

To me, the title question can be formulated as: How can a dynamic object-oriented language like Python potentially be untyped? My answer is that there are basically just two ways how to achieve this:

  1. Via the dynamically typeduntyped equivalence.
  2. By inventing nonsense behavior (a generalized nasal demon) for method resolution.

Let us consider Python, Ruby and JavaScript as three significant languages that are relevant to the title question. All of them are popular dynamic OO languages in which "classes are objects" and in all of them there is a built-in TypeError class. (JavaScript is sometimes claimed to be classless. We will follow here JavaScript: The Definitive Guide by D. Flanagan, where there classes are explicitly mentioned, including the TypeError class. Moreover, the notion of a class is explicitly supported in ECMAScript 6.) At this point let us emphasize, that assembly or the C language mentioned in some answers or comments are NOT relevant.

Having established the group of three languages that are relevant to the question we can observe that the dynamically typeduntyped equivalence has already been established in the literature. There are probably more statements that the 3 languages are dynamically typed but there is also considerable amount of statements about their untypedness or typelessness. As for Python: (Py1), (Py2), (Py3), (Py4), (Py5), (Py6) (Py7), (Py8), (Py9), (Py10), (Py11), (Py12). As for Ruby: (Rb1), (Rb2), (Rb3), (Rb4), (Rb5). As for JavaScript: (Js1) – quoted by Wikipedia in the opening sentence, (Js2), (Js3). Moreover, there are also articles ((eq1), (eq2)) where the equivalence (dynamically typeduntyped) is suggested explicitly.

The reason behind this terminological paradox is that removing what is called run-time type checking would mean not throwing any TypeErrors which is absurd. (Why then throw any Error at all?) One can imagine that there is no detection of possible invalid states as I have mentioned before. (To give an example also for JavaScript, the x.__proto__ = x assignment is a request to form a cycle in inheritance, and thus rejected as a TypeError.) But without the (absurd) concept of nasal demons, I cannot imagine what should a "removal of type enforcement" mean in the o.m() case of method resolution failure. What is a possible meaning of calling a missing method?

I also think that the 1 + "a" example of typechecked Python code provided in the answer by Gilles should be presented for a user-defined function, rather than a built-in one. In this case, disallowing type checking would in fact mean to prohibit the programmer from using isinstance (issubclass, type(), __class__, ...) and/or throwing Errors. This is absurd too.

The only non-absurd removal of a type constraint that I am aware of is disregarding the number of method arguments – something that is realized in JavaScript (probably due to missing default argument values). But I doubt that this should be the dividing point - the reason why JavaScript should be considered untyped while Python and Ruby dynamically typed.

Summed up, the dynamically typeduntyped equivalence can be used as a mnemonic for an inconsistency in the terminology. It is better than to pretend that the notions are well established.

| cite | improve this answer | |
$\endgroup$
  • 2
    $\begingroup$ Yes, there's a difference between dynamically typed vs untyped languages. Or, at least, there's an important difference between an exception being thrown (as in dynamically typed languages) vs the operation producing nonsense results or creating a security vulnerability or allowing nasal daemons (as in untyped languages). $\endgroup$ – D.W. Feb 19 '16 at 18:12
  • 1
    $\begingroup$ Anyway, answer this seems to be relying on an assumption that doesn't seem justified to me. I don't see why you think type enforcement of type (1) can't be removed. It surely can -- see the C programming language. Whether you think it's clear what should happen in that case or not doesn't change the fact that it is possible to remove those kinds of type enforcement. (Doesn't even your last sentence implicitly acknowledge that in fact it is possible to remove type enforcement of type (1)?) $\endgroup$ – D.W. Feb 19 '16 at 18:15
  • $\begingroup$ @D.W. Yes, it is possible to remove type enforcement of type (1) by e.g. returning a nil object on method/attribute resolution failure. But this is just hiding an error. It does not make much sense. I would just like to know an example of a dynamic language that is neither statically typed nor dynamically typed. $\endgroup$ – paon Feb 19 '16 at 18:30
  • 1
    $\begingroup$ With the nomenclature I'm aware, untyped languages usually refer to languages such as assembly, BCPL, BLISS, Forth which have only one type and the operations decide how the values are interpreted, dynamically typed languages have the values carry type information and operations check it and may do different things for different types. $\endgroup$ – AProgrammer Feb 19 '16 at 19:10
  • 3
    $\begingroup$ Also, I think you're placing an awful lot of reliance on votes on a particular SO question. First off, votes don't necessarily mean all that much -- they sometimes measure popularity more than correctness. Second, that SO question has all sorts of context that make a different situation than this question; context matters, and you shouldn't draw sweeping conclusions about what "untyped language" means, as the context is relevant. $\endgroup$ – D.W. Feb 19 '16 at 20:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.