Why Is Dynamic Typing So Often Associated with Interpreted Languages?
Asked Answered
N

7

47

Simple question folks: I do a lot of programming (professionally and personally) in compiled languages like C++/Java and in interpreted languages like Python/Javascript. I personally find that my code is almost always more robust when I program in statically typed languages. However, almost every interpreted language I encounter uses dynamic typing (PHP, Perl, Python, etc.). I know why compiled languages use static typing (most of the time), but I can't figure out the aversion to static typing in interpreted language design.

Why the steep disconnect? Is it part of the nature of interpreted languages? OOP?

Novia answered 8/9, 2009 at 13:0 Comment(0)
M
59

Interesting question. BTW, I'm the author/maintainer of phc (compiler for PHP), and am doing my PhD on compilers for dynamic languages, so I hope I can offer some insights.

I think there is a mistaken assumption here. The authors of PHP, Perl, Python, Ruby, Lua, etc didn't design "interpreted languages", they designed dynamic languages, and implemented them using interpreters. They did this because interpreters are much much easier to write than compilers.

Java's first implementation was interpreted, and it is a statically typed language. Interpreters do exist for static languages: Haskell and OCaml both have interpreters, and there used to be a popular interpreter for C, but that was a long time ago. They are popular because they allow a REPL, which can make development easier.

That said, there is an aversion to static typing in the dynamic language community, as you'd expect. They believe that the static type systems provided by C, C++ and Java are verbose, and not worth the effort. I think I agree with this to a certain extent. Programming in Python is far more fun than C++.

To address the points of others:

  • dlamblin says: "I never strongly felt that there was anything special about compilation vs interpretation that suggested dynamic over static typing." Well, you're very wrong there. Compilation of dynamic languages is very difficult. There is mostly the eval statement to consider, which is used extensively in Javascript and Ruby. phc compiles PHP ahead-of-time, but we still need a run-time interpreter to handle evals. eval also can't be analysed statically in an optimizing compiler, though there is a cool technique if you don't need soundness.

  • To damblin's response to Andrew Hare: you could of course perform static analysis in an interpreter, and find errors before run-time, which is exactly what Haskell's ghci does. I expect that the style of interpreter used in functional languages requires this. dlamblin is of course right to say that the analysis is not part of interpretation.

  • Andrew Hare's answer is predicated on the questioners wrong assumption, and similarly has things the wrong way around. However, he raises an interesting question: "how hard is static analysis of dynamic languages?". Very very hard. Basically, you'll get a PhD for describing how it works, which is exactly what I'm doing. Also see the previous point.

  • The most correct answer so far is that of Ivo Wetzel. However, the points he describes can be handled at run-time in a compiler, and many compilers exist for Lisp and Scheme that have this type of dynamic binding. But, yes, its tricky.

Merrilee answered 11/9, 2009 at 22:27 Comment(9)
A very thoughtful, well done answer. Thanks very much. There's a lot of sloppy vocab (mine included) around this subject, it seems.Novia
Paul, you are awesome for selecting this PhD thesis. I admire you because although I love python, I lament its lack of static typing, or at least the ability to do static type checking before running. Let me know how it is going.Briefing
"Java's first implementation was interpreted, and it is a statically typed language." -- you're using an unusually broad definition of interpreted there. Java's first implementation was compiled to a virtual machine instruction set, just the same as it usually is today. The virtual machine didn't translate to native code as it does today, but as the instruction set used is one that is suitable for direct hardware implementation, I don't think it can really be described as interpreted.Intrigue
@Intrigue If original Java is compiled because it translates the source into bytecodes which are then interpreted, then so is modern Ruby, which also translates the source into bytecodes which are then interpreted. The line between compiled and interpreted is a little fuzzy these days.Wiatt
@WayneConrad - Yes. But there are distinctions. The most important from a user perspective is that Java has a separate compilation step which reports errors (other than syntax errors). This provides a somewhat different user experience compared to Ruby. Also, there as I point out, there is no need for Java bytecodes to be interpreted. They can be executed directly on hardware, and the hardware for doing so even became ubiquitous at one point (many "feature phone" type mobile phones have processors that can directly run Java bytecode, so that's an installed base of millions of units).Intrigue
Another distinction, although possibly less important, is that Ruby bytecode is much higher level than Java's. Compare the ruby bytecode instructions newhash, toregexp, or concatstrings with the most complex java bytecode instructions which are probably checkcast or lookupswitch. There's a substantial difference. Also contrast ruby's opt_plus (which accepts two arguments, identifies their numeric type, then executes the appropriate addition operation for that type) with java's iadd, ladd, dadd and fadd, each of which is specialised for a single numeric type.Intrigue
@Intrigue Wonderful comments, thank you. I'm wondering what question I could ask (that would remain open) that would give you a place to expand upon those as an answer.Wiatt
There's a very simple test for whether an implementation is interpreted. It is "is the code executed by an interpreter".Merrilee
"how hard is static analysis of dynamic languages?". Meh. That depends. You can very easily create a language with all the major benefits of dynamic languages and as far as people can tell it's a dynamic language but it's statically checked.Extort
S
5

Interpreted languages use dynamic typing because there is no compilation step in which to do the static analysis. Compiled languages do static analysis at compilation time which means that any type errors are reported to the developer as they work.

It is easier to understand if you consider that a statically typed language has a compiler that enforces type rules outside the context of execution. Interpreted languages are never analyzed statically so type rules must be enforced by the interpreter within the context of execution.

Shepp answered 8/9, 2009 at 13:4 Comment(6)
Thanks for your answer, Andrew. I understand this, but I still don't see a reason why interpreted languages can't do this analysis at runtime.Novia
Thanks again for the update. However, perhaps I'm not addressing my question properly. Re: the last sentence of your explanation, is there any limitation that prevents interpreted languages from being analyzed due to the inherent nature of interpreted languages? Or is it just a popular language design choice?Novia
"Static" means that an operation is performed at compile-time; "dynamic" means it is deferred to runtime. Interpreted languages have no (manual) compilation step. You can and do have dynamically-typed languages which strong typing, though (e.g., Python).Waldowaldon
daveslab: consider a language like Python. There is no static type checking in Python, but the language is still strongly typed. This means that each value has a single, well-defined type at runtime. Doing "foo" + 3 throws a TypeError at runtime. So some languages do the analysis at runtime.Fugato
Speaking as somebody currently writing an interpreter for a static language that performs a type-checking step on module load, I think I can safely say that the notion that this isn't possible is clearly incorrect. It might be an unusual choice, as doing this isn't necessary for implementing an interpreter (while for a compiler it would usually be a strict requirement to have a such a phase), but there's no reason you can't do it, if that's how you want your language to behave.Intrigue
"there is no compilation step in which to do the static analysis" That's not true. Python at least performs syntax checking at compile timeDour
A
5

I think it's because of the nature of interpreted languages, they want to be dynamic, so you CAN change things at runtime. Due to this a compiler never exactly knows what's the state of the program after the next line of code has been excecuted.

Imagine the following scenario(in Python):

import random
foo = 1

def doSomeStuffWithFoo():
    global foo
    foo = random.randint(0, 1)

def asign():
    global foo
    if foo == 1:
        return 20
    else:
        return "Test"


def toBeStaticallyAnalyzed():
    myValue = asign()

    # A "Compiler" may throw an error here because foo == 0, but at runtime foo maybe 1, so the compiler would be wrong with its assumption
    myValue += 20


doSomeStuffWithFoo() # Foo could be 1 or 0 now... or 4 ;)
toBeStaticallyAnalyzed()

As you can hopefully see, a compiler wouldn't make any sense in this situation. Acutally it could warn you about the possibility that "myValue" maybe something else than a Number. But then in JavaScript that would fail because if "myValue" is a String, 20 would be implictily converted to a String too, hence no error would occur. So you might get thousands of useless warnings all over the place, and i don't think that that is the intend of a compiler.

Flexibility always comes with a price, you need to take a deeper look at your program, or program it more carefully, in other words you are the COMPILER in situations like the above.

So your solution as the compiler? - Fix it with a "try: except" :)

Atherosclerosis answered 8/9, 2009 at 14:34 Comment(3)
Yes, it's pretty clear that if you do some dynamic type stuff in a dynamically typed language the compiler couldn't figure out the type. What's not clear is why should the language be dynamically typed, if it were designed such that each function's return and variable were of a declared type it could then check for the type.Pimply
The language is dynamically typed BECAUSE of its design, the design is so that you can change stuff at runtime, that's what's making it so dynamic. If a function would return a typed value, what happens then if you asign that to a untyped variable? More dynamic conversion! So the typing in one half of the language wouldn't make it any better. In the end you have the choice betwenn static typed, more machine checked code and dynamic typed more human check code, which is also more dynamic, because in the end a human is still more intelligent than a compiler.Atherosclerosis
I understand what you're saying. If the language had an int main(){return 0} or an int main(){return "a"} just because the design allows you to declare a type doesn't mean it will be enforced. But it could be enforced if that was part of the design. However either of these cases could be done as a compiled or interpreted language. Nothing says an interpreted language wants to be dynamic, but I agree that if I wrote an interpreted language, I'd rather make it dynamic.Pimply
S
4

Compilers + Static types = efficient machine code
Compilers + Dynamic types = inefficient machine code

Consider the following pseudocode:

function foo(a, b) {
    return a+b
}

A Static language will be able to know (by declaration or inference) that a and b are integers, and will compile down to

%reg = addi a,b

or something similar, anyway.

A compiler for a dynamic language would have to emit code to
1. Check they types of a and b
2. handle each case or combination of cases

%reg1 = typeof a
beq %reg1, int, a_int_case
beq %reg1, float, a_float_case
beq %reg1, string, a_string_case

label a_int_case
%reg1 = typeof b
beq %reg1, int, a_int_b_int_case
beq %reg1, float, a_int_b_float_case
beq %reg1, string, a_int_b_string_case

label a_int_b_int_case
%out = addi a,b
goto done

label a_int_b_float_case
%tmp = mkfloat a
%out = addf %tmp,b
goto done

... Etc. I can't finish

While you could generate smarter machine code than that, you wouldn't be able to help generating lots of code -- That makes compilation not a major win for a dynamic language.

Since interpreters are much easier to write, and compilation doesn't do you much good, why not write an interpreter?

(Just-in-time compilers actually have type information, and can compile right down to the single statement. They actually have more information than static type systems, and can theoretically do even better. All assembler is simulated; Any resemblance to real code that could run on a real machine is purely coincidental.)

Sedum answered 27/10, 2009 at 13:19 Comment(2)
I think it would be better to say that interpreter + dynamic language = inefficient programs. Compiling dynamic languages is typically efficient, when its possible.Merrilee
@Paul: Sure, interpreters are inefficient, but we all knew that. My point was that naively compiling an interpreted language will produce a program that's not much better than interpreting; Compiling dynamic languages is not "typically" efficient -- making it efficient takes a lot of work, so is not typically done.Sedum
P
1

Maybe it's because one of my main interpreted languages is Perl and one of my compiled languages is Objective-C, but I never strongly felt that there was anything special about compilation vs interpretation that suggested dynamic over static typing.

I think it's clear that both sides are looking at the other and thinking, "There's some advantages to that." It's easier in several applications to get some dynamic type flexibility, while it can be easier to maintain something that is statically typed and enforced.

I disagree with Andrew Hare's explanation though. While a purely interpreted language would have to add in a preprocessing step, and hence not be purely interpreted in order to warn the programmer before execution of static typing errors, it doesn't preclude throwing a type error at run-time as it occurs. So lacking compilation does not mean no static type checking may occur. But since getting a type error at run-time isn't as useful as getting one at compilation, or during a preflight check, I can see how the "advantage" of static typing in that situation can seem to be more of a nuisance, and thus get thrown out in favor of the advantages dynamic typing bring.

If you knew from the outset that you prefer to keep your types static because personally you write better more maintainable code as a result, and you were designing your interpreted language, nothing should stop you from designing the language as a statically typed one.

To quote the interpreted languages wiki article "Theoretically, any language may be compiled or interpreted, so this designation is applied purely because of common implementation practice and not some underlying property of a language."
There's a decent wiki article on just the typing.

Pimply answered 8/9, 2009 at 15:26 Comment(6)
"So lacking compilation does not mean no static type checking may occur. But since getting a type error at run-time isn't as useful as getting one at compilation" - If the type error occurs at runtime, then it's a dynamic check, not a static check. But there are dynamic languages that perform type checking at runtime, and will throw type errors -- this is a dynamically-typed language with strong types.Waldowaldon
@Pimply Thanks for your detailed answer. This was the answer I was looking for. @Waldowaldon I don't think that's neccesarily true. You can pass around void pointers in C++ and cast them as whatever you like and get a "type" error (not in the strict sense).Novia
What's "not necessarily true"? That a check that occurs at runtime is a dynamic check? That is true: it's the definition of "dynamic" (as opposed to "static", which occurs before runtime).Waldowaldon
If the runtime tells you your type is wrong, it's because the typing is not dynamic and is static. We're not talking about the type checking being dynamic or static. That's why I linked the wiki article.Pimply
I know i'm late to this, but I always thought the definition of Static was unchanging and Dynamic was changing. Thus, dynamic-ly typed languages allow changing the type (adding and removing methods, etc) at runtime, and static-ly typed languages don't. Nothing to do with when the check occurs, I see no reason that you couldn't do a static check at runtime (yes I realize I'm late to this)Lustrum
@JimDeville The usual definition of dynamic in this context is something along the lines of "variables don't have a predefined type but can be changed to different types at run time". The fact that some dynamic languages allow you to modify the definition of a type at runtime is interesting, but isn't part of the definition of a dynamic language (cf Smalltalk or Objective-C, both of which are dynamic but do not have this capability).Intrigue
C
-1

Dynamically typed interpreted languages give you more freedom in the way you program. It allows for meta programming to be feasible. It allows for variables to be created at run time. Allows for anonymous hashes and anonymous arrays to be created at any given time during run time without ever previously declaring anything before hand. It allows for undetermined information to be inputed into a hash without ever declaring all the keys in advance. You can have subroutines created from undetermined random input. You can feed a program code also that can be run dynamically. Interpreted languages free the chains from which limits programming in general. You are limited to what you type in the source file with statically typed languages. You can do more with less in a dynamically typed language.

Most robots being made today deal with interpreted languages more because information needs to be determined at runtime and new variables need to be made to store this information at runtime. Machine learning is based around information being interpreted. We ourselves as humans are interpreters which is why robots are being designed that way. The future really is interpreted. Of course you need statically typed languages to build interpreters so statically typed languages will never go away unless interpreters are built in assembly code in the future. Most interpreters are built upon statically typed languages these days.

Interpreted languages excel in a dynamic environment. If you can interpret new code/information at run time then why not. If your really good at dynamically programming then you can create code that can create variables and hashes without ever typing everything out. You can reduce the amount of lines drastically if your working with huge amounts of data. You can use a data dumper to print out all your information because interpreted languages usually keep track of the type of variables at runtime allowing for this to be possible. You can't do this in barebones c++. The only time c++ and c knows what's going on is at compile time. After that your on your own unless you implement something yourself.

Who wants to be tied down so much to the source file these days especially when your working in dynamic environments. All your doing is limiting your potential. Once your neck deep in dynamically interpreted code and you go back to any statically typed language you will find it harder to dumb down your code because your still thinking in a limitless mindset. Your mind needs to return back to being limited again to what's typed in the source file.

In the way of programming styles: Statically typed code produces static results. Dynamically typed code produces dynamic or static results.

If your going to be programming something that never changes behavior other than what is known then statically typed languages are great for that. If your dealing with dynamic behavior then dynamically typed languages are better suited for those cases. Everything depends on the situation mostly.

Every language has their ups and downs. Just gotta choose and pick wisely.

Cantonese answered 13/1, 2018 at 23:14 Comment(0)
C
-2

I think static typing makes it easier for compilers and that's the main (if not only) reason that it's present in compiled languages.

For interpreted languages it's easier to assume that variables don't have type (only values have) because they are thought of not as a placer for the data that must fit inside but rather label for the data that floats somewhere on the heap.

If programmer want's he can always assert that variable holds a value of given type (for example on assignment). There is no reason to build it into the language. Of course that not the same kind of control that you have for compiled languages.

You probably could have language in which you have to explicitly declare type of each variable but if you don't it's much easier to do interesting things that with static typing would require from programmer very carefully crafted complex generic types.

On the other hand. Do you know any dynamically typed compiled (statically, not JIT) language?

Contrastive answered 8/9, 2009 at 13:18 Comment(2)
Factor is a compiled language with dynamic typing.Waldowaldon
Add Scheme to the mix too.Dour

© 2022 - 2024 — McMap. All rights reserved.