Static vs. Dynamic Typing

Please compose all posts in Emacs.

Moderators: phlip, Moderators General, Prelates

Rysto
Posts: 1460
Joined: Wed Mar 21, 2007 4:07 am UTC

Static vs. Dynamic Typing

Postby Rysto » Tue Apr 17, 2007 2:51 am UTC

I've seen a couple of threads partially hijacked by this discussion, so I figured it deserved its own thread.

I come down very much on the static typing side of the debate. I just don't see any real benefits to dynamics typing. It's an established that that the earlier in the development cycle you catch a bug, the cheaper it is to fix it. Indeed, it's often orders of magnitude cheaper. So this is a huge win for static typing.

Second, the compiler can report all type errors in a program at once. In a dynamically typed language, the first type error will kill your program. So you have to run your test, fix the first bug, run the test, fix the next bug, etc, etc. I've done this, and it's a long and tedious process. I wasn't even working on a particularly large program, either.

Third, explicitly annotating your code with types enhances readability(this obviously only applies to languages like C or Java, and not languages like Haskell with type inference). It's common to see C function prototypes with only the types, because that(along with the name) makes it clear what the function does(case in point: memcpy). I find it far easier to read code with explicit type annotations, because the type of a variable gives a lot of hints as to its purpose. It's easier to simply look at the declaration of a variable to see its type than have to read the code and infer its type.

Fourth, static type error messages are typically better(and rarely worse) than dynamic error messages. An error detected at compile-time will be detected at the exact line that caused the problem(except in the rare case of a bad declaration, but that's not to hard to detect, and a good compiler will make it obvious what the error is). Dynamic type errors don't always show up where they occur. The classic example is inserting an integer into what's meant to be a list of strings. You don't get an error on insertion, but on removal, which can occur a long way from the real error. This kind of error used to drive Java programmers nuts before generics were introduced.

Fifth, static typing is more efficient. Doing type-checking at run-time takes time. Perhaps not much time, but over the course of a large program it can add up, especially in an object-oriented program, as they tend to contain a lot of small function calls.

Sixth, on the issue of duck typing: I really don't see the purpose. Except when overloaded operators are allowed, I can't see how it would be all that useful. Using interfaces is a much cleaner solution, and it has the added advantage that it enforces API changes. In very rare cases, you might run into a situation where you want generic code that can call methods on library classes and classes that you've written yourself, and the library doesn't contain an appropriate interface or base class. In this case, you're stuck with using the Adapter pattern, which isn't all that awful a solution.

User avatar
torne
Posts: 98
Joined: Wed Nov 01, 2006 11:58 am UTC
Location: London, UK

Re: Static vs. Dynamic Typing

Postby torne » Tue Apr 17, 2007 10:55 am UTC

Rysto wrote:It's an established that that the earlier in the development cycle you catch a bug, the cheaper it is to fix it. Indeed, it's often orders of magnitude cheaper. So this is a huge win for static typing.

I find my bugs while running unit tests, which I do every time I finish writing something, at the same point I would run a compiler if I was writing C. So, not earlier in the cycle.

Second, the compiler can report all type errors in a program at once. In a dynamically typed language, the first type error will kill your program.

True. However, static checking tools usually exist which will identify them all at once (Python has pychecker and pylint). Also, it's rare for me to have more than one type error in a single cycle of edit -> test, because it's really not that common - the average in each cycle is well below one.

Third, explicitly annotating your code with types enhances readability(this obviously only applies to languages like C or Java, and not languages like Haskell with type inference).

I find the opposite in most cases - type information is usually redundant to a human being in C and Java, and serves only to make prototypes longer and less readable. Maybe that's just me.

Fourth, static type error messages are typically better(and rarely worse) than dynamic error messages.

True, unless you use a static checker which typically identifies the error at the same point a compiler would.

Fifth, static typing is more efficient. Doing type-checking at run-time takes time.

Type checking at runtime is not required in duck typed languages. There are optimisations that can't be performed, true (optimising away polymorphic dispatch, etc) but these are counter to the whole purpose of duck typing and so are usually considered an acceptable casualty. Also, runtime partial specialisers like Psyco (or the JVM) can take care of those optimisations for many cases where it's desirable to increase performance. So, YMMV.

Sixth, on the issue of duck typing: I really don't see the purpose.

Faster development, more readable code, easier to make changes to existing code, runtime type mutability.

Using interfaces is a much cleaner solution, and it has the added advantage that it enforces API changes.

Enforced interfaces are usually also available in duck typed languages - it can be done in Python pretty easily. However, interfaces aren't a solution to all the problems solved by duck typing - it's not just about not having to declare your interfaces, it's a different philosophy to begin with.

Most duck typed languages allow instances and classes to acquire new methods and members at runtime. There is no equivalent to this in most statically typed languages, and it's more useful than you might think. The ability to, say, replace a method with a static member value at runtime, or override a method for a specific instance, is quite handy. Another useful ability is being able to respond to *all* methods, not just the ones defined in advance, say, if you are implementing a proxy object which forwards its calls to another object. There's even more bizarre and amusing tricks (like generating methods from some other source at startup or call time, or dynamically replacing methods with different implementations) but they're not used as often. :)

Ah well. I've used most kinds of language (well, most languages, full stop) over the years, and there are plenty of times when I would pick a statically typed language, often specifically because of that aspect and the perfectly good reasons you state. I'm a kernel developer, amongst other things, and I depend on strong typing there because it's just not feasible to cover every case in testing. However, there are plenty more times when I would pick a duck typed language, and for me that includes any general application programming task (clients, servers, web apps, whatever). I would rather write shorter, clearer code and spend my time making sure tests cover as large a proportion of the requirements as possible.

User avatar
william
Not a Raptor. Honest.
Posts: 2418
Joined: Sat Oct 14, 2006 5:02 pm UTC
Location: Chapel Hill, NC
Contact:

Re: Static vs. Dynamic Typing

Postby william » Tue Apr 17, 2007 11:12 am UTC

Rysto wrote:I come down very much on the static typing side of the debate. I just don't see any real benefits to dynamics typing. It's an established that that the earlier in the development cycle you catch a bug, the cheaper it is to fix it. Indeed, it's often orders of magnitude cheaper. So this is a huge win for static typing.

How does static typing help that way? I've found that dynamically typing things makes the cycle itself faster, which helps even more because you can now have more cycles.
Second, the compiler can report all type errors in a program at once. In a dynamically typed language, the first type error will kill your program. So you have to run your test, fix the first bug, run the test, fix the next bug, etc, etc. I've done this, and it's a long and tedious process. I wasn't even working on a particularly large program, either.

I have never had a type error like that. A type of error I have had was the "put the wrong type in because I was too busy on the algorithms".
Third, explicitly annotating your code with types enhances readability(this obviously only applies to languages like C or Java, and not languages like Haskell with type inference). It's common to see C function prototypes with only the types, because that(along with the name) makes it clear what the function does(case in point: memcpy). I find it far easier to read code with explicit type annotations, because the type of a variable gives a lot of hints as to its purpose. It's easier to simply look at the declaration of a variable to see its type than have to read the code and infer its type.

No, it really isn't. I absolutely hate having just the types there, I'd much rather prefer having just the name of the variable, as if you're programming well it should describe the purpose rather than just giving hints. For example, would you rather have gammacdf(float, float, float, float) or gammacdf(left, right, shape, scale)? And that's not even a contrived example, that's one I could see myself using.


Also, having to explicitly annotate your code is the most fucking annoying thing possible when you're adding code or features to a program, adds *that much more effort* when you're refactoring, and doesn't really help debugging. If you misspell something, an error will come up either way.

Though, type inference is a very neat idea, but it sounds incompatible with good polymorphism.

Fourth, static type error messages are typically better(and rarely worse) than dynamic error messages. An error detected at compile-time will be detected at the exact line that caused the problem(except in the rare case of a bad declaration, but that's not to hard to detect, and a good compiler will make it obvious what the error is). Dynamic type errors don't always show up where they occur. The classic example is inserting an integer into what's meant to be a list of strings. You don't get an error on insertion, but on removal, which can occur a long way from the real error. This kind of error used to drive Java programmers nuts before generics were introduced.

Unless I want to mix integers and strings in my list. For example, if I were implementing Dots and Boxes, I could have an array of arrays, each array containing either an integer number of walls the box is surrounded by or the string value of the letter the player who took the box wants in his or her boxes.
Fifth, static typing is more efficient. Doing type-checking at run-time takes time. Perhaps not much time, but over the course of a large program it can add up, especially in an object-oriented program, as they tend to contain a lot of small function calls.

No it can't. No studies have shown static typing to provide any efficiency. The real efficiency is in having a profiler, something that is much easier in interpreted languages.

Sixth, on the issue of duck typing: I really don't see the purpose. Except when overloaded operators are allowed, I can't see how it would be all that useful. Using interfaces is a much cleaner solution, and it has the added advantage that it enforces API changes.

Cleaner == add crappy code that doesn't do any shit at all? And I don't want API changes enforced, that just breaks things when I'm trying to update or refactor, thereby doubling the time it takes to change my code. And with no real benefit.
In very rare cases, you might run into a situation where you want generic code that can call methods on library classes and classes that you've written yourself, and the library doesn't contain an appropriate interface or base class. In this case, you're stuck with using the Adapter pattern, which isn't all that awful a solution.

Patterns are not a good solution when the good solution is using less code, and more readable code, in the first place.
SecondTalon wrote:A pile of shit can call itself a delicious pie, but that doesn't make it true.

User avatar
evilbeanfiend
Posts: 2650
Joined: Tue Mar 13, 2007 7:05 am UTC
Location: the old world

Postby evilbeanfiend » Tue Apr 17, 2007 11:17 am UTC

meh, both have their place.
in ur beanz makin u eveel

User avatar
Yakk
Poster with most posts but no title.
Posts: 11115
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Yakk » Tue Apr 17, 2007 1:41 pm UTC

I want a language with both.

I use the compiler phase to do guarantees that I can check staticly. Ideally I move as many guarantees that I can get the compiler to check into this phase.

In effect, I use it as a form of code generation: it generates statically understandable comments, it generates statically understandable guarantees, and it is expressed in a language common to all C++ developers, which means when working on a project that contains integrated code from literally 100s of different people and dozens of distinct code bases there is still some sanity.

Not that I wouldn't mind having duck typing as well: in C++, you can do half-assed duck typing via templates. I would prefer the support to be better. Heck, C++'s static typing needs work: there is no reason why it shouldn't support a poly-interface object pointer (well, I guess you could implement it, but it would be ugly: C++ template programming needs VarArgs). I want to be able to create and modify classes at runtime (not only instances of classes).

EvanED
Posts: 4331
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI
Contact:

Re: Static vs. Dynamic Typing

Postby EvanED » Tue Apr 17, 2007 2:31 pm UTC

william wrote:And I don't want API changes enforced, that just breaks things when I'm trying to update or refactor, thereby doubling the time it takes to change my code. And with no real benefit.


API changes are always "enforced." Whether they are enforced by the compiler giving you type errors, enforced by the program throwing a type exception at runtime, or enforced by the program making an incorrect assumption and producing wrong results, if you change the API, you have to change all the code that uses it. (Okay, I guess you could have a call or something that doesn't exercise the change; that doesn't *need* to be changed, but it is in some sense a latent error.)

If your API change also changes the type of the function and you're in a statically-typed language... well, your compiler makes sure that those fall into the first category rather than the second two.

I'll post my thoughts on this at some point too. I actually almost created this thread maybe a week ago.

(EDIT: mostly just changed wordings)

User avatar
bitwiseshiftleft
Posts: 295
Joined: Tue Jan 09, 2007 9:07 am UTC
Location: Stanford
Contact:

Re: Static vs. Dynamic Typing

Postby bitwiseshiftleft » Tue Apr 17, 2007 6:00 pm UTC

Rysto wrote:I come down very much on the static typing side of the debate. I just don't see any real benefits to dynamics typing.


I generally prefer static typing too, but there are certainly benefits to dynamic typing. In fact, I quote from the Godfather of Haskell, today, on the Haskell mailing list:

Simon Peyton-Jones wrote:Well-typed programs don't go wrong, but not every program that never goes wrong is well-typed. It's easy to exhibit programs that don't go wrong but are ill-typed in ... any ... decidable type system. Many such programs are useful, which is why dynamically-typed languages like Erlang and Lisp are justly popular.


There are lots of reasons to like duck typing. Most notably, if you have some type Duck, and you add another type Goose, you can pass Geese to methods that expect Ducks, but only use the fact that their arguments can fly() and waddle(). If you want static typing, you have to make a new class CanFlyAndWaddle, and make Goose and Duck subclass/instance it, which is a pain in the rear. It is particularly a pain in the rear if you don't have multiple inheritance.

Static typing can do almost the same things as dynamic typing, but it requires a nontrivial amount of glue code to do it, and it often requires the designers to foresee what types and classes will be needed (as above, with CanFlyAndWaddle).

On the other hand, static typing (particularly in Haskell, which is pure, and in Java, which shows most exceptions that code can throw) gives top-level authors strong guarantees of what library code can and can't do, and vice versa. That's why Haskell has safe STM (software transactional memory), written as a library rather than a compiler add-on, and most other languages don't have STM at all.

User avatar
Yakk
Poster with most posts but no title.
Posts: 11115
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Yakk » Tue Apr 17, 2007 6:13 pm UTC

Staticly typed languages tend to suck in that way.

Ie, one should be able to:

Code: Select all

typedef HasMethod(bool Fly(int height)) CanFly;
typedef HasMethod(bool Waddle(double skew)) CanWaddle;
interface union(CanFly, CanWaddle) CanFlyAndWaddle;

bool DoDuckThing( CanFlyAndWaddle& duck );


Note that the above contract could be staticly enforced in C++. The language just doesn't support that kind of syntax. Basically, the above is static ducktyping: the difference being that the contract is enforced at compile time instead of run time.

Toss in the ability to create named interfaces inline in a class as easily as methods, and you end up with a staticly typed language that has much better sugar than C++.

C++ does static ducktyping with templates currently. Sadly, template support in most compilers sucks. :) We need better support for export templates.

Rysto
Posts: 1460
Joined: Wed Mar 21, 2007 4:07 am UTC

Re: Static vs. Dynamic Typing

Postby Rysto » Tue Apr 17, 2007 6:16 pm UTC

bitwiseshiftleft wrote:There are lots of reasons to like duck typing. Most notably, if you have some type Duck, and you add another type Goose, you can pass Geese to methods that expect Ducks, but only use the fact that their arguments can fly() and waddle(). If you want static typing, you have to make a new class CanFlyAndWaddle, and make Goose and Duck subclass/instance it, which is a pain in the rear.

I see this argument all the time, but I've never hit an actual situation in which this has come in useful. Maybe I'm just better at foreseeing how my programs will evolve; maybe I haven't been involved in a software project long enough to see this kind of evolution. In any case, is it *really* that hard to do:

Code: Select all

interface CanFlyAndWaddle {
    void fly();
    void waddle();
}
...
class Duck implements CanFlyAndWaddle {
...


I really don't see how this is a big issue. Now, if you don't have control of the Duck class, things get annoying, but how often does that happen?

User avatar
Yakk
Poster with most posts but no title.
Posts: 11115
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Yakk » Tue Apr 17, 2007 7:10 pm UTC

With 15 properties, there are 105 pairs of properties, 455 triples of properties, 1365 quads of properties. Overall, there are 32,767 (2^15-1) nonempty different combinations of those properties.

It would be relatively easy to add into C++:

Code: Select all

void DoStuff( (CanFly && CanWaddle)& bird );

ie, a variable being multiple different types at once. Does such a construct exist in C#?

User avatar
bitwiseshiftleft
Posts: 295
Joined: Tue Jan 09, 2007 9:07 am UTC
Location: Stanford
Contact:

Re: Static vs. Dynamic Typing

Postby bitwiseshiftleft » Tue Apr 17, 2007 7:16 pm UTC

Rysto wrote:I see this argument all the time, but I've never hit an actual situation in which this has come in useful. Maybe I'm just better at foreseeing how my programs will evolve; maybe I haven't been involved in a software project long enough to see this kind of evolution.


I'm still a student, so I usually work on smaller, shorter-lived research projects. In most of my projects, the design permits stable, well-defined module boundaries; this often isn't the case in the real world. Nonetheless, I've run into this problem a few times, in several statically-typed languages.

A lot of my previous argument was paraphrased from a friend of mine, who worked in industry for several years (mostly writing games), and he said that this problem is pretty severe in his experience.

Rysto wrote:In any case, is it *really* that hard to do:

Code: Select all

interface CanFlyAndWaddle {
    void fly();
    void waddle();
}
...
class Duck implements CanFlyAndWaddle {
...


I really don't see how this is a big issue. Now, if you don't have control of the Duck class, things get annoying, but how often does that happen?


Well, let's see. You have to change some of your methods and local variables from Duck to CanFlyAndWaddle. You have to import CanFlyAndWaddle everywhere. This is annoying in a large project, and common to pretty much every statically typed language. Also, your company might control Duck, but if a lot of people use it, commits there might be bothersome.

In Java specifically, the syntax for requiring that method arguments (or worse, return values or local variables) implement multiple interfaces (y'know, CanFly, CanWaddle, CanMigrate, KidsLikeToFeed) and/or extensions is somewhat nasty, wasn't available until Java 5, and isn't always expressive enough (in particular, you can't say things like "extends this" or "returns this;" see for example Object.clone()). Also, interfaces can't have methods, which often causes those methods to go in random, inappropriate files.

I'm generally not a fan of Java's type system and syntax, but other languages have their own problems. In Haskell and O'Caml, for instance, classes and types are different (similar to how interfaces and classes and types are all different in Java, but worse), and so you have to define wrappers for them, which is painful.

Edit: ninja'd!

Yakk wrote:It would be relatively easy to add into C++:

Code: Select all

void DoStuff( (CanFly && CanWaddle)& bird );

ie, a variable being multiple different types at once. Does such a construct exist in C#?


I don't know C#, but this is almost exactly how Haskell's objects work. Haskell's classes are basically interfaces with methods, and you (are encouraged to) declare functions as

Code: Select all

doStuff :: (CanFly duck, CanWaddle duck) => duck -> someReturnType
doStuff aDuck = ...


Java supports a similar concept, but in a messier and less consistent way.
Last edited by bitwiseshiftleft on Tue Apr 17, 2007 7:25 pm UTC, edited 1 time in total.

Rysto
Posts: 1460
Joined: Wed Mar 21, 2007 4:07 am UTC

Postby Rysto » Tue Apr 17, 2007 7:19 pm UTC

Well, let's see. You have to change some of your methods and local variables from Duck to CanFlyAndWaddle. You have to import CanFlyAndWaddle everywhere. This is annoying in a large project, and common to pretty much every statically typed language. Also, your company might control Duck, but if a lot of people use it, commits there might be bothersome.

Only in the code you want to generify. Code specific to Ducks can be left alone.

User avatar
bitwiseshiftleft
Posts: 295
Joined: Tue Jan 09, 2007 9:07 am UTC
Location: Stanford
Contact:

Postby bitwiseshiftleft » Tue Apr 17, 2007 7:30 pm UTC

Rysto wrote:Only in the code you want to generify. Code specific to Ducks can be left alone.


True enough, but code dependencies might require you to generify a lot of stuff.

Furthermore, where does the generic code go, and what does it look like? It can't go in Duck, because it works for everything that CanFlyAndWaddle. It can't go in CanFlyAndWaddle, because interfaces can't have methods. So it has to go in a static utility class, which you'll have to import in addition to CanFlyAndWaddle. That means that it can't have instance methods at all; everything "method" of a CanFlyAndWaddle has to be a static method instead (i.e. a function). Furthermore, you have to call it with prefix notation: CanFlyAndWaddleUtils.navigate(this, ...).

(end of rant)

Rysto
Posts: 1460
Joined: Wed Mar 21, 2007 4:07 am UTC

Postby Rysto » Tue Apr 17, 2007 9:00 pm UTC

Hm, does this really belong in CS instead of Coding? We're discussing implementation issues here, not type theory. Not that it really matters.

User avatar
davean
Site Ninja
Posts: 2498
Joined: Sat Apr 08, 2006 7:50 am UTC
Contact:

Postby davean » Tue Apr 17, 2007 9:52 pm UTC

Rysto wrote:Hm, does this really belong in CS instead of Coding? We're discussing implementation issues here, not type theory. Not that it really matters.


Impact is part of language design. What features where when and why. Softer side, but I'd say so. Also, with any luck, people will actually talk about what advantages a type system does give you, for example, some systems prove security constrains with it, preventing information leaks. There are quite a few very good papers on the use of type systems for all sorts of constraints.

User avatar
Strilanc
Posts: 646
Joined: Fri Dec 08, 2006 7:18 am UTC

Postby Strilanc » Wed Apr 18, 2007 3:23 am UTC

The main issue I have with most dynamically typed languages is the way they let you just go "a = 2" without any previous declaration of a. For example, python lets you do this and Visual Basic 6 would let you do this unless you put "Option Explicit" at the top of every file.

Allowing random introduction of variables completely screws me over because typo-ing a variable name (especially in a hard-to-read way) SCREWS YOU until you run over the code line-by-line with an interpreter.

Visual Basic was especially bad at this because it would even let you use r-values that hadn't been assigned or declared yet.

I think as many of the benefits of dynamic typing should be moved to static typing as possible. "void foo(methods(run,jump,walk) rjw) { rjw.run(); }" would be absolutely wonderful.

For example, would you rather have gammacdf(float, float, float, float) or gammacdf(left, right, shape, scale)? And that's not even a contrived example, that's one I could see myself using.

That's an issue of someone not including variables names in their declaration (only an issue when you have headers like with C) or your IDE not including variables names (which is ridiculous). What if, instead of "float,float,float,float" you were given "4 arguments" (which is sometimes what you get from error messages)? Clearly this doesn't affect the usefulness of dynamic typing: the error message just isn't very clear.
Don't pay attention to this signature, it's contradictory.

User avatar
torne
Posts: 98
Joined: Wed Nov 01, 2006 11:58 am UTC
Location: London, UK

Postby torne » Wed Apr 18, 2007 10:51 am UTC

Alky wrote:The main issue I have with most dynamically typed languages is the way they let you just go "a = 2" without any previous declaration of a. For example, python lets you do this and Visual Basic 6 would let you do this unless you put "Option Explicit" at the top of every file.

Static checkers catch this in Python in almost every case - local names which are bound but not used are an obvious sign of a mistake. Also, if what you think is a rebinding is actually a new binding, surely your code will break, and you'll notice? It's usually pretty clear what's gone wrong.

Allowing use of undefined names is braindead, though.

I think as many of the benefits of dynamic typing should be moved to static typing as possible. "void foo(methods(run,jump,walk) rjw) { rjw.run(); }" would be absolutely wonderful.

This is what static checkers for dynamic languages do for you, only without the requirement that you annotate your code with unnecessary pseudo-type information. If the function foo calls methods run, jump and walk on its argument, the static checker makes a note of this, and then flags any case where you pass foo an object that doesn't have a run, jump and walk method as a possible error. This is much, much better than doing it the way you describe because your way, it can trivially be wrong - what if someone adds a call to rjw.swim() somewhere in foo, and forgets to update the pseudo-type information in the prototype?

That's an issue of someone not including variables names in their declaration (only an issue when you have headers like with C) or your IDE not including variables names (which is ridiculous).

The point is, what extra information do the types add? If it says gammacdf(left, right, shape, scale) already, then what knowledge are you gaining by adding 'float' before each one? Anyone who would want to use such a function is going to know that those arguments are floats. Sometimes yes, they do add information (which in dynamically typed languages is relegated to API documentation) but this isn't the common case by a long way.

Static checking gives you almost every benefit of having types (with regards to catching programming errors) without losing any of the cool, funky powers of duck typing. You write less code, it's easier to read, it takes you less time, and it's checked to a similar (or higher) standard by automatic tools. The whole development cycle ends up faster, which means, as william said, you can go around it more times. Test more, refine more, improve more. :)

Rysto
Posts: 1460
Joined: Wed Mar 21, 2007 4:07 am UTC

Postby Rysto » Wed Apr 18, 2007 12:39 pm UTC

I don't get your argument. If the only solution to the issues that we've raised is to write a new tool that checks everything statically, is our language crying out for static typing?

User avatar
taylor_venable
Posts: 30
Joined: Mon Apr 09, 2007 7:22 pm UTC
Location: United States
Contact:

Re: Static vs. Dynamic Typing

Postby taylor_venable » Wed Apr 18, 2007 12:43 pm UTC

Rysto wrote:Third, explicitly annotating your code with types enhances readability(this obviously only applies to languages like C or Java, and not languages like Haskell with type inference). It's common to see C function prototypes with only the types, because that(along with the name) makes it clear what the function does(case in point: memcpy). I find it far easier to read code with explicit type annotations, because the type of a variable gives a lot of hints as to its purpose. It's easier to simply look at the declaration of a variable to see its type than have to read the code and infer its type.

Even with type-inferred languages like Haskell, SML, and OCaml, annotating types of functions is extremely useful.

First, the code is more readable because you can quite easily see the types of the arguments, and the result. For example, Haskell's map function has type "(a -> b) -> [a] -> [b]" which takes (1) a function from something of type `a` to something of type `b` (2) a list of something of type `a`. Finally, it produces a list of something of type `b`. If you didn't already know from the name of the function, this might give you some idea of what the function does, as well as where and how to use it.

Second, it helps detect errors in your code. As "The Little MLer" recommends to beginning SML programmers, it's best to figure out the type of a function before you write it. That is, think of how the function works in terms of what it consumes (parameters) and what it produces (result). Use this annotation when you're writing your function. If you later get an error from the compiler that the inferred type of the function doesn't match the explicit type, then you've got an error in your code, which you might not otherwise have caught if you didn't annotate it explicitly, and relied solely on the compiler to infer the type.
My website: http://real.metasyntax.net:2357/
"I have never let my schooling interfere with my education." (Mark Twain)

User avatar
taylor_venable
Posts: 30
Joined: Mon Apr 09, 2007 7:22 pm UTC
Location: United States
Contact:

Postby taylor_venable » Wed Apr 18, 2007 12:58 pm UTC

Alky wrote:The main issue I have with most dynamically typed languages is the way they let you just go "a = 2" without any previous declaration of a. For example, python lets you do this and Visual Basic 6 would let you do this unless you put "Option Explicit" at the top of every file.

Allowing random introduction of variables completely screws me over because typo-ing a variable name (especially in a hard-to-read way) SCREWS YOU until you run over the code line-by-line with an interpreter.

Visual Basic was especially bad at this because it would even let you use r-values that hadn't been assigned or declared yet.

I agree that the use of unassigned variables in r-values is stupid. Even if your language allows this (like Perl, where such variables are evaluated to "undef"), it should allow you to turn such "features" off (in Perl, you can "use warnings" and "use strict", which are both always a good idea).

But as far as undeclared variables in the l-value goes, well that's part of dynamic typing. Variables aren't typed by name, like "variable x is always an int" -- instead they're typed by value, "variable x has the type of whatever data it's holding". Therefore, you can't declare a variable in Python: it makes no sense for a variable to exist, yet not have a value, because it also therefore has no type. Now, you could say that assigning None is like variable declaration, except then you've still made an assignment and the variable still has a type (that of NoneType), so it's really no different than using the variable for the first time and happening to assign None instead of 2. This is how temporary variables work in Ruby, when using a code block: they are assigned nil, and take on the NilClass type when the block is entered.

So as you can see, there's no point in annotating the type of a variable in a dynamically typed language, because types in such a language are determined by assignment of a value, not by the variable itself. In Visual Basic 6 the story is different, though, because it's not dynamically typed, if I recall correctly. Visual Basic 6 and Python have different type systems, so to treat them the same is really trying to compare apples and oranges.
My website: http://real.metasyntax.net:2357/
"I have never let my schooling interfere with my education." (Mark Twain)

EvanED
Posts: 4331
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI
Contact:

Postby EvanED » Wed Apr 18, 2007 2:17 pm UTC

torne wrote:
I think as many of the benefits of dynamic typing should be moved to static typing as possible. "void foo(methods(run,jump,walk) rjw) { rjw.run(); }" would be absolutely wonderful.

This is what static checkers for dynamic languages do for you, only without the requirement that you annotate your code with unnecessary pseudo-type information. If the function foo calls methods run, jump and walk on its argument, the static checker makes a note of this, and then flags any case where you pass foo an object that doesn't have a run, jump and walk method as a possible error. This is much, much better than doing it the way you describe because your way, it can trivially be wrong - what if someone adds a call to rjw.swim() somewhere in foo, and forgets to update the pseudo-type information in the prototype?


Then the compiler complains because rjw isn't guaranteed to have a swim() function.

BTW, this is a different, in some ways more lightweight, in some ways not, presentation of a new feature that will be in C++0x called Concepts. (Actually Concepts are slightly different, but not much.)

That's an issue of someone not including variables names in their declaration (only an issue when you have headers like with C) or your IDE not including variables names (which is ridiculous).

The point is, what extra information do the types add? If it says gammacdf(left, right, shape, scale) already, then what knowledge are you gaining by adding 'float' before each one? Anyone who would want to use such a function is going to know that those arguments are floats.


That's because your case is overly simplistic, and actually you're asking the wrong question.

I find that thinking about the type of a function often provides a LOT of help in how it should be written; no so much in how you call a function.


Just out of curiosity, anyone know how a tool like CQual would work for dynamically typed languages? Anyone know of any?

User avatar
evilbeanfiend
Posts: 2650
Joined: Tue Mar 13, 2007 7:05 am UTC
Location: the old world

Postby evilbeanfiend » Wed Apr 18, 2007 2:22 pm UTC

if you are only considering built in types you are about 20 years out of date on typed languages. the usefulness comes in when you can have not

Code: Select all

float length;


but

Code: Select all

metres length1;
feet length2;


and guarantee they don't get mixed . safe + unsafe strings is the other canonical example
in ur beanz makin u eveel

User avatar
yy2bggggs
Posts: 1261
Joined: Tue Oct 17, 2006 6:42 am UTC

Postby yy2bggggs » Wed Apr 18, 2007 3:24 pm UTC

evilbeanfiend wrote:...and guarantee they don't get mixed . safe + unsafe strings is the other canonical example

Or even more useful, when you can do quite the opposite; that is, have metres length1 and feet length2, and mix them, and have it do the right thing; same with safe and unsafe strings.

Rysto
Posts: 1460
Joined: Wed Mar 21, 2007 4:07 am UTC

Postby Rysto » Wed Apr 18, 2007 3:28 pm UTC

I saw a prototype language from Sun that allowed you to attach units to numbers. So you'd write something like:

x = 4m

Could be very useful if implemented properly. Would've saved us a Mars Lander.

User avatar
Yakk
Poster with most posts but no title.
Posts: 11115
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Yakk » Wed Apr 18, 2007 4:45 pm UTC

You can do a bit of this (attaching units and dimensions to values) with C++ templates. It isn't pretty, however...

I've written up a dimensional toolkit for C++ using templates.

Sadly, C++ delegation sucks, so it takes some code generation/macros to get it to work "right".
Last edited by Yakk on Wed Apr 18, 2007 5:19 pm UTC, edited 1 time in total.

User avatar
Yakk
Poster with most posts but no title.
Posts: 11115
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Yakk » Wed Apr 18, 2007 5:18 pm UTC

EvanED wrote:BTW, this is a different, in some ways more lightweight, in some ways not, presentation of a new feature that will be in C++0x called Concepts. (Actually Concepts are slightly different, but not much.)


Ok, Concepts are neat. I knew I should be paying more attention to C++0x. :)

To those who care: C++ templates are currently staticaly ducktyped. This causes ridiculously annoying errors.

Concepts are a way to generate a ducktype contract in the declairation of the template, instead of implicitly in the implementation. It can also restrict the template body to obey the ducktype restrictions listed in the template declairation.

(that is understanding from about 20 minutes of reading, so it may be incorrect or incomplete).

User avatar
torne
Posts: 98
Joined: Wed Nov 01, 2006 11:58 am UTC
Location: London, UK

Postby torne » Thu Apr 19, 2007 11:06 am UTC

Rysto wrote:I don't get your argument. If the only solution to the issues that we've raised is to write a new tool that checks everything statically, is our language crying out for static typing?

No, because it does the static checking without requiring any type information in the source, and it degrades gracefully in the face of things which can't be statically verified such as "has this object had the correct methods dynamically added at runtime to allow this call to be valid". It's also optional, so you don't have to bother if you don't care (when you're writing a quick hack).

EvanED wrote:Then the compiler complains because rjw isn't guaranteed to have a swim() function.

Ok, ok, that was too simple an example :) Seriously though, having that type annotation there is pointless if the compiler is going to do that kind of verification anyway - the compiler could just infer that the required methods are run(), jump() and walk(), and automatically complain about any calls which have a parameter not meeting these requirements.

What if the object you're passing as rjw doesn't have a run() method at compile time, but will by the time the function call is encountered?

EvanED wrote:I find that thinking about the type of a function often provides a LOT of help in how it should be written; no so much in how you call a function.

Not in imperative programming languages, for me. In a functional language, deciding what the type of a particular function is is pretty useful information, as other people pointed out re ML. In an imperative language I'm far more interested in what the arguments mean than what types are involved. The types are supposed to take care of themselves.

yy2bggggs wrote:
evilbeanfiend wrote:...and guarantee they don't get mixed . safe + unsafe strings is the other canonical example

Or even more useful, when you can do quite the opposite; that is, have metres length1 and feet length2, and mix them, and have it do the right thing; same with safe and unsafe strings.

Automatic mixing of types in that way is trivial to do in dynamically typed languages. Derive from int, or str, or whatever, and implement the appropriate transformations (or errors).

User avatar
evilbeanfiend
Posts: 2650
Joined: Tue Mar 13, 2007 7:05 am UTC
Location: the old world

Postby evilbeanfiend » Thu Apr 19, 2007 1:04 pm UTC

ya, its all trivial to do in any language. still both dynamic + static typing have their place.
in ur beanz makin u eveel

User avatar
yy2bggggs
Posts: 1261
Joined: Tue Oct 17, 2006 6:42 am UTC

Postby yy2bggggs » Thu Apr 19, 2007 1:34 pm UTC

torne wrote:Automatic mixing of types in that way is trivial to do in dynamically typed languages. Derive from int, or str, or whatever, and implement the appropriate transformations (or errors).

I was replying about potential advantages of typed languages personally; however, your claim is way too general to possibly be true. The term "dynamically typed language" does not entail a language which has this capability (neither does the term "statically typed language").

Static typing mainly has the advantage of preventing contract errors. The use of a static type checking tool in a dynamic language is either prone to error, or complete--if it's the latter, then it's irrelevant to discuss what can be done in dynamic languages using such tools, as this effectively creates a statically typed language (and a more dangerous one, since you can forget to apply the tool).

User avatar
torne
Posts: 98
Joined: Wed Nov 01, 2006 11:58 am UTC
Location: London, UK

Postby torne » Fri Apr 20, 2007 10:34 am UTC

yy2bggggs wrote:I was replying about potential advantages of typed languages personally; however, your claim is way too general to possibly be true. The term "dynamically typed language" does not entail a language which has this capability (neither does the term "statically typed language").

Ok, ok, technically no, but the commonly used ones (and the root of the discussion, python) do. It seems a logical thing to provide when types are largely a matter of implementation inheritance and not interface contracts.

Static typing mainly has the advantage of preventing contract errors. The use of a static type checking tool in a dynamic language is either prone to error, or complete

Of course it's useless to have a complete type checker, so yes, they miss things. The idea is that it's an acceptable price for the benefits. It's not acceptable in every use - there are plenty of times when contracts really have to be enforced as hard as possible, and a static type system is a handy tool for this - but I was only ever claiming that for general app programming, this isn't usually the case. There's plenty of places to use statically typed languages too. The majority of people arguing the case for static typing here don't seem to recognise a similar position, is all - they give me the impression that they don't see *any* use for dynamic typing outside of "quick hack scripting".

User avatar
hotaru
Posts: 1045
Joined: Fri Apr 13, 2007 6:54 pm UTC

Postby hotaru » Fri Apr 20, 2007 11:37 am UTC

one of the things i like so much about jscript .net is that variables can be either statically typed or dynamically typed, and you can mix them in a single program...

this works:

Code: Select all

import System;
var a=42;
print(a);
a='some string';
print(a);


but this gives a "Type mismatch" error at compile time:

Code: Select all

import System;
var a:int=42;
print(a);
a='some string';
print(a);

User avatar
yy2bggggs
Posts: 1261
Joined: Tue Oct 17, 2006 6:42 am UTC

Postby yy2bggggs » Fri Apr 20, 2007 3:14 pm UTC

torne wrote:The idea is that it's an acceptable price for the benefits. It's not acceptable in every use - there are plenty of times when contracts really have to be enforced as hard as possible, and a static type system is a handy tool for this - but I was only ever claiming that for general app programming, this isn't usually the case.


It's an acceptable price for the benefits, when it is. When it is not, it is not.

If you're trying to defend Python, that may be part of the problem. Think of it this way. If you're designing a language from scratch, and contracts are important, should you prefer to enforce them statically or dynamically? The decision is a no brainer--statically, of course! Would there be reasons to want to choose dynamic typing instead? Sure--but they would be related to an entirely different concern (maybe there's something more important than contracts?) The phrasing "there are plenty of times when contracts really have to be enforced as hard as possible" is weaseling around this. Contracts are contracts--enforcing them is enforcing them. There's really no such things as enforcing them "hard"--it's just enforcing them period. What we're really talking about is enforcing them at compile time versus enforcing them at run time.

As for what is true in "general app programming", I call hogwash. There is a sufficient number of scenarios in widely varying environments where statically typed languages are the way to go such that there is no possible way you can claim that it's "usually the case" that dynamically typed languages are better.

User avatar
torne
Posts: 98
Joined: Wed Nov 01, 2006 11:58 am UTC
Location: London, UK

Postby torne » Mon Apr 23, 2007 10:57 am UTC

yy2bggggs wrote:It's an acceptable price for the benefits, when it is. When it is not, it is not.

Well, yah. That was what I stated too.

If you're trying to defend Python, that may be part of the problem.

Well, that's where the discussion started - someone proposed writing a mail server in Python and several folks thought this was a bad idea for various reasons; I disagreed.

If you're designing a language from scratch, and contracts are important, should you prefer to enforce them statically or dynamically? -snip-

If you require them to be enforced statically then use static typing. I'm not trying to weasel around anything - I am trying to argue the position that most of the time it doesn't matter if interface contracts are enforced statically or not, and thus you should, most of the time, ignore that criteria altogether and make the choice based on other concerns.

As for what is true in "general app programming", I call hogwash. There is a sufficient number of scenarios in widely varying environments where statically typed languages are the way to go such that there is no possible way you can claim that it's "usually the case" that dynamically typed languages are better.

Anecdotal, on both sides. It's been my experience throughout my career that there's very little benefit to static typing. I've hardly ever seen mistakes in code (mine or other people's) which are detected (or would've been detected) by static type checking. Even when kernel programming, I've found I'm many, many times more likely to need to insert kludges (unchecked casts, etc) to deal with the inadequacy of C++'s static typing system than I am to make an error that would go unnoticed were said typing system not there.

So, yes, from my experience I do claim, and feel justified in claiming, that dynamically typed languages are better. If there was a way to objectively prove what the best tool for a given job was, people would've found it by now :)

User avatar
Yakk
Poster with most posts but no title.
Posts: 11115
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Yakk » Mon Apr 23, 2007 1:47 pm UTC

On the other hand, I write reasonably complex programs that are designed to fail if I screw up the static type checking.

When programming in a dynamicly typed language, I'm forced to manually verify all of the things I would staticly check using language constructs.

On top of that, the auto-documentation of static type checking, detailing the format of the data expected to each arguement, means that analyzing code for how to modify it and/or improve it is easier. One can fix this by documenting the dynamic code extensively: but it isn't automatic.

User avatar
Strilanc
Posts: 646
Joined: Fri Dec 08, 2006 7:18 am UTC

Postby Strilanc » Mon Apr 23, 2007 3:35 pm UTC

Static languages have better auto-complete in IDEs. This is a god-send when learning a new language or working on a new program. It's like the environment does half the work for you.

I've also adjusted my programming style over the years, like Yakk, to take advantage of static typing. When I write things in python it drives me UP THE WALL when an error that should be caught by a type checker gets through. It can take hours to find these, because they can be caused almost ANYWHERE (typo in an l-value? screwed for an hour).

That's not to say there are cases where it doesn't work: it's hard to write a memory manager in C without a helping of (int)s and (int*)s (you can do this with an int[] and offsets instead of pointers).
Don't pay attention to this signature, it's contradictory.

User avatar
evilbeanfiend
Posts: 2650
Joined: Tue Mar 13, 2007 7:05 am UTC
Location: the old world

Postby evilbeanfiend » Mon Apr 23, 2007 3:56 pm UTC

the following tcl snippet demonstrates both the power of dynamic typing and some of the problems

Code: Select all

expr [join $list +]


it's a really succinct and clever way to sum a list of numbers. however the performance is likely worse that other methods as it has to convert between numbers strings and lists, and it is not robust if given a list that doesn't contain numbers.
in ur beanz makin u eveel

User avatar
yy2bggggs
Posts: 1261
Joined: Tue Oct 17, 2006 6:42 am UTC

Postby yy2bggggs » Mon Apr 23, 2007 4:14 pm UTC

torne wrote:
As for what is true in "general app programming", I call hogwash. There is a sufficient number of scenarios in widely varying environments where statically typed languages are the way to go such that there is no possible way you can claim that it's "usually the case" that dynamically typed languages are better.

Anecdotal, on both sides.

I didn't give an anecdote; I countered with a more useful claim. The term "general app programming" is very loaded and gives a false impression to the degree that it's useless to talk about it, as does any assessment as to what is usually required.

As for Python, and in particular its suitability for writing a mail server, I've no problems.

User avatar
Yakk
Poster with most posts but no title.
Posts: 11115
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Yakk » Mon Apr 23, 2007 8:58 pm UTC

evilbeanfiend wrote:the following tcl snippet demonstrates both the power of dynamic typing and some of the problems

Code: Select all

expr [join $list +]


it's a really succinct and clever way to sum a list of numbers. however the performance is likely worse that other methods as it has to convert between numbers strings and lists, and it is not robust if given a list that doesn't contain numbers.


C++0x and run-time duck-typing

Code: Select all

external template<typename T>
requires Container<T>, Summable<T::value_type>
T::value_type sum_container( T const& ) {
  // ... implementation
}


See:
http://conceptgcc.wordpress.com/tag/c/
for a blag-worth-reading.

User avatar
torne
Posts: 98
Joined: Wed Nov 01, 2006 11:58 am UTC
Location: London, UK

Postby torne » Tue Apr 24, 2007 9:57 am UTC

Yakk wrote:On the other hand, I write reasonably complex programs that are designed to fail if I screw up the static type checking.

When programming in a dynamicly typed language, I'm forced to manually verify all of the things I would staticly check using language constructs.

I've never written code to verify things that a typing system could've verified for me; what would be the point? If those things needed verifying, I would've used types. Unit tests pass, thus all statically checkable problems must have already been eliminated. I write programs that don't pass their test suite if I've made any error, statically checkable or otherwise. Seems easier. :)

On top of that, the auto-documentation of static type checking, detailing the format of the data expected to each arguement, means that analyzing code for how to modify it and/or improve it is easier. One can fix this by documenting the dynamic code extensively: but it isn't automatic.

I usually assume that if what you're supposed to pass for an argument isn't obvious, the argument (or the function) is named wrong. All comments/documentation/etc which are required to understand the code are a symptom of the code being wrong; correct code should be obviously correct.

Rysto
Posts: 1460
Joined: Wed Mar 21, 2007 4:07 am UTC

Postby Rysto » Tue Apr 24, 2007 12:11 pm UTC

torne wrote:I usually assume that if what you're supposed to pass for an argument isn't obvious, the argument (or the function) is named wrong. All comments/documentation/etc which are required to understand the code are a symptom of the code being wrong; correct code should be obviously correct.

:roll:

What kind of toy programs are you writing?


Return to “Religious Wars”

Who is online

Users browsing this forum: No registered users and 5 guests