Benefits Of Dynamic Typing Discussion

On BenefitsOfDynamicTyping objections are raised:


There are more useful programs than there are useful statically typed programs: Almost all type systems are decidable and hence must reject programs that are valid but cannot be proven valid under the type system.

Which statically typed languages?

Sorry, I don't follow. What part of the above paragraph does this refer to?

You deleted that part: "Most statically typed languages don't allow this, instead requiring side-effects on parameters to store the data and return a status code from the function (e.g. CeeLanguage and JavaLanguage )."

The reason for the question is that both C++ and Java, the two most widely used statically-typed languages, both have a NULL object that can be abused for that purposed, if you wanted to, as described in the next paragraph.

OK, but it's not exactly the same. NULL doesn't have any particular meaning, whereas the EOF object means exactly the end of file.

If you insisted on using C++ in a Smalltalk way, then you could return a new "character object" for each character read, and NULL for the end-of-file condition. But the output-parameter method is superior in many respects (not just that it's often an order of magnitude faster. It also makes it obvious that the end-of-file condition exists and must be checked for.)

True enough, but substituting NULL for end of file loses information. Say that I want to read a bunch of files and convert them into a stream of characters and EOF objects. Can't do that in C++ unless the character object and EOF object belong to the same type (say, they share an ancestor in the class hierarchy) which is a somewhat bogus relationship. So to get the same freedom in a statically typed language that we get in a dynamically typed language we have to jump through hoops. It can be done (naturally, because all these languages are TuringComplete) but it is inconvenient.

If it can always be done (as you point out) the question is, what support does the language give you for describing your intentions completely, and then helping you stick to them?

For example:

  struct Result
  {
bool succeeded;
char value;
  };

class File { Result readCharacter(); };

for (;;) { Result r = f.readCharacter(); if (!r.succeeded) break;

// do something with r.value }

Though personally, I have no major religious objection to output parameters, so I'd prefer this because it's so succint without being confusing:

  class File
  {
boolean readCharacter(char &c);
  };

char c; while (f.readCharacter(c)) // do something with c

OK, another example (just came up at work). Say you want to specify the starting time for a timer thread. You can either specify a time in minutes since midnight, or "right now". Now the way it has been done is by making negative numbers mean "right now". Yuck. I'd rather be able to include the symbol 'now amongst the possible values (does it show I'm a LispLanguage hacker at heart? ;-). I could define a class like you've done above with Result, but i) I can't be bothered ii) it's not my code (no CollectiveCodeOwnership here :( ). Here's some example code illustrating the differences:

Dynamic typing

  if (rebuildTime == 'now) {// Allow the slight liberty with C++ syntax
rebuildTime = currentTime();
  }

Static typing

  class RebuildTime {
boolean now;
int rebuildTime;
  }

if (rebuildTime.now) { rebuildTime.rebuildTime = currentTime(); rebuildTime.now = false; }

I prefer the former. YMMV. BTW, I'm telling some slight lies here. You can statically type many of these supposedly dynamically typed programs if you have a SoftTyping system. I'm not against StaticTyping per se, but against the loss of flexibility it entails. I prefer being able to statically type what can be statically typed, and leaving the rest dynamically typed.

Negative numbers is definitely yucky, but to my mind, no more mucky than defining a new symbol to stand in for it. In C++ you could use:

  const int nNow = -1;

Why is that (a) especially different from your perceived ideal, or (b) especially better than using -1 directly?

To my mind, the major problem is that all code that uses time values has to check for the special case. We can concentrate that complexity into a class:

  class CleverTime
  {
int m_nTime;

public: CleverTime(int nTime = -1) : m_nTime(nTime) {}

int get() { if (m_nTime == -1) return currentTime();

return m_nTime; } };

If you construct one of those with no parameter, it represents the "current time" at the point of use. If you supply the time as a parameter, then that's the time it represents. When you want to use it, call get(), which takes care of handling the special case.

  CleverTime now;
  CleverTime lunchtime(983240); // that's a guess! depends when you have lunch...

// Both cases are used exactly the same: cout << now.get() << lunchtime.get();

It would now be trivial to extend the system so that -2 meant "this time yesterday" for example.

  class CleverTime
  {
int m_nTime;

public: CleverTime(int nTime = -1) : m_nTime(nTime) {}

int get() { if (m_nTime == -1) return currentTime();

if (m_nTime == -2) return (currentTime() - SECONDS_PER_DAY);

return m_nTime; } };

I may be unwittingly side-stepping your point and instead talking about abstract datatypes (which obviously work just as well in Smalltalk)! But that's just how I'd deal with that example.

Gak. This is an object-oriented system, use it:

  class Time {virtual int get() = 0;}

class KnownTime :: Time { int time; public: KnownTime(int _time) {time = _time}; int get() {return time;} }

class NowTime :: Time { public: int get() {return currentTime();} }

You might want to add factory methods, etc., and adding Yesterday is just another simple class.

You can do the same with the file-reading example: AbstractCharacter has subclasses Character and FileEndCharacter. --BillTrost


Final example, from SchemeLanguage code I'm hacking at the moment. I'm parsing some text (it's another WikiWikiClone) and want to tokenise the text. I either get symbols, indicating formatting, or strings, indicating text, back from the tokeniser. How do you represent that in a statically typed language? If you wrap the tokens in a class, with a variable to distinguish the type, you're not statically typing any more. (Similarly, the response way at the top of this page with the struct Result isn't really statically typed anymore, as it relies on the dynamic property of the succeeded bool to determine the type). I think this is the best example so far so if others have no objection I'll delete the other examples.

(Using Java because I'm more comfortable with it.)

  public class Token {
public static final Token PARAGRAPH_MARKER = new Token();
public static final Token HORIZONTAL_RULE = new Token();
// other formatting tokens go here

public static final Token END_OF_STREAM = new Token();

public class StringToken extends Token { public String text; } }

public class Tokenizer { public Tokenizer(InputStream stream) { /* ... */ }

public Token nextToken() { /* ... */ } }

public class WikiClone { public void tokenize(InputStream stream) { Tokenizer tokenizer = new Tokenizer(stream); for (Token t = tokenizer.nextToken(); t != Token.END_OF_STREAM; t = tokenizer.nextToken()) { if (t instanceof StringToken) { handleText( ((StringToken) t).text ); } else { handleFormatting(t); } } } }

And that handles both the token example and the EOF example in one go! ;-)

I'm inclined to believe that when a method/function wants to return different types in different situations, it really means that the code is asking for a new type. The instanceof check in the above code smells bad to me, but the dynamic-typing equivalent would require a similar check (except it would be checking whether the token was a String or some other arbitrary type). If I was really fleshing this out, I would actually replace the conditional logic with a polymorphic method on Token. -- TimMoore


These problems and benifits seem quite trivial and easily solved even in the lowly c++.

A start time should be specified in a timer object. Use constructor overloading to make this simple. A constructor with no time arg means now. A constructor with a time arg means then.

The whole null issue is just odd. If you want to say something then say it. Don't try to overload null and then complain that it doesn't say what you want. Use an apply object. Passing in the character worked fine. I think it's better anyway not to overload status and values.


In response to TimMoore: Union types aren't dynamic typing. Using the timer thread example and Haskell to illustrate the difference...

  data Time = Now | Seconds Int

startTime Now = currentTime startTime (Seconds s) = ... calc time however ...

so what's the difference? The use of dynamic typing above didn't add 'now to the set of values, it added EVERYTHING; so one could pass 'Apple or (lambda (x) x) or 3.14159 or, a typo, 'noww just as well. -- Darius

Sorry, I think I'm still missing something. Probably because I'm not familiar with Haskell. What happens when you pass 'Apple or 'noww or something else? How does this improve on BillTrost's C++ example? What's the benefit of dynamic typing here? -- TimMoore

Hmm, I may have misattributed what I was responding to. I was responding to the statement,

How do you represent that in a statically typed language? If you wrap the tokens in a class, with a variable to distinguish the type, you're not statically typing any more. (Similarly, the response way at the top of this page with the struct Result isn't really statically typed anymore, as it relies on the dynamic property of the succeeded bool to determine the type)

The code I wrote doesn't demonstrate a benefit of dynamic typing; Haskell is a statically-typed language (in a fairly hardcore way). So to answer one of your questions (which should be obviated now), if you pass, or rather attempt to pass, the equivalent of 'Apple or 'noww to startTime, the type checker yells at you. The Haskell code is to demonstrate that just because you make a decision on what to do based on what you are passed, doesn't mean that you are using "dynamic typing". Some dynamic typing advocates seem to confuse union types (a la Haskell) with dynamic types (dynamic types can be considered a sort of universal union type, but a square is a rectangle, a rectangle isn't a square). For example, one argued that checking if the returned value of read (in C say) is <0 to test for EOF is dynamic typing. This is just nonsense, either the value is <0 == EOF, or it's >=0 == character. In a dynamically typed language, say Lisp, 'Apple or (1 3 4) or #t could also be returned, this obviously couldn't happen in the C version (well I guess you could consider it returning a boolean in C ;). Or using the example in the quote I'm responding to, Result obviously isn't dynamic typing because again, Result isn't going to give you a list or a double.

-- Darius


See PhlipOnBenefitsOfDynamicTyping


CategoryDiscussion


EditText of this page (last edited December 11, 2004) or FindPage with title or text search