(See also:
DynamicTyping,
DavidThomasOnTheBenefitsOfDynamicTyping,
PhlipOnBenefitsOfDynamicTyping)
Also see
BizarroStaticTypingDebate,
BenefitsOfDynamicTypingDiscussion,
WhenIsManifestTypingConsideredaGoodThing,
IsDynamicTypingSufficientlyEfficient,
RuntimeTypeMutability.
This is very easy to reason about. The set of dynamically typed programs is a straightforward superset of the set of statically typed programs. Suppose we have two languages, LS, and LD. They have the same syntax and semantics, except that LS is statically typed and LD is dynamically typed.
[[The set of dynamically typed programs is also straightforwardly a subset of the set of statically typed programs, by the embedding described
farther down this page. The straightforward embedding of a statically typed language into a dynamically typed langauge requires reimplementing the type checking
of the static language. How else could you capture type-directed behavior like the "type classes" in
HaskellLanguage?]]
[[Below it is stated that the implementation of LD performs a static analysis, and can emit the diagnostic "I cannot prove that this program is free of type errors". This implies that LD is using
SoftTyping,
not DynamicTyping.]]
What is the difference between them? It is this: all LS programs are valid LD programs, but the converse is not true. Therefore LD makes more kinds of programs expressible.
This is absurd. Assuming both languages are TuringComplete, both must have an equal number of expressible programs. What you're saying here is that LD can express that same number of programs in more ways. In essence, you've proven that dynamic typing makes things more complex by adding more ways to do the same thing. I actually like dynamic languages but I think this is not the way to demonstrate they are in some way better than static languages. --
BrianRobinson
Expressible is not the same as computable. To express something is to write some sequence of symbols to make a well-formed program. Turing completeness tells us that all sufficiently powerful languages can compute the same functions, but not necessarily by expressing the same program in all of them. What I'm saying here is that the same program is actually expressed. The actual program written in LS is also a program of LD, without modification. The same expression.
- This assertion ignores TypefulProgramming, where the exact meaning of the expression varies based upon static types - especially the types of operations described after the operation in question. It is important to consider not just whether the program in LS is a program of LD, but also whether it is the same program in LD as it was in LS.
Both LS and LD can actually be the same language implementation, LSD. The only difference between the two languages is a boolean configuration flag. Prior to runtime, LSD processes the source code and performs deep inference to find type errors. When it finds type errors, it rejects the program. When it fails to find type errors, it has to decide whether the program is in one of two categories: known to be free of type errors, or not known to be free of type errors. And here is where the configuration flag comes in: if no type errors were inferred, but the program is not known to be free of type errors, and the flag says "behave like LS", then the program must be rejected. But if the flag says "behave like LD" then the program can be run anyway. However, there must still be safety, therefore when behaving like LD, the implementation must associate run-time type information with each object, so that errors can be caught. When behaving like LS, the type information can be discarded, since the program is proven to be free of type errors.
- Note that the notion that the source-code is statically preprocessed also violates many of the supposed benefits of DynamicTyping, such as easier support for scripting. Is DynamicTyping being defended here? or is LD something else entirely? (SoftTyping has been suggested.)
It should be noted that for a sufficiently flexible type system, there will be programs for which TypeInference is undecidable. This has been proven in the literature.
Note that LS has absolutely no safety advantage with respect to LD, because the same static analysis is done. The only difference is that LD accepts some programs after emitting the diagnostic "I cannot prove that this program is free of type errors", whereas LS adds "and therefore I'm rejecting it because I have no way to ensure its safety once it is executing".
Except that most languages LD don't perform the "deep type inference" (or even shallow type inference, for that matter) which you speak of - most of 'em will accept code like the following (in PseudoCode):
define a := "This is a string";
define b := a * 74.329;
In other words, languages with dynamic typing (many of 'em, at least) depend on the runtime to catch all
type errors.
It should also be noted that TypeInference is being confused with TypeChecking. Ensuring that a programmer's annotations are consistent is a far easier problem than automatically generating type annotations to a program at compile time. The latter is still an area of active research.
The only reason for such rejection is so that the LS implementor does not have to support a run-time typing mechanism, and implement the complex optimization strategies for dynamic programs. Valid LS programs can be spun into blind machine code with no run time type checks, and no extra bits of representation in any object.
In some cases, it goes further. In some application areas, such as MissionCritical software, proving the absence of type errors is the key concern; not the performance improvements associated with StaticTyping.
All religious debates between static and dynamic typing advocates, at least the clueful ones, hinge on whether or not this is a reasonable tradeoff. One side insists that the flexibility is not needed, and that it is inefficient; it is often observed, however, that programmers introduce their own error-prone, clumsy and inefficient dynamic typing hacks in static programs. Also, modern dynamic languages have good optimizing compilers, and provide ways to add declarations to code "hotspots" to assist the compiler.
Likewise, most statically-typed OO languages include some level of dynamic-typing feature, even it is just typesafe downcasts (which either demonstrate the conversion is correct, or fail gracefully.)
There is a deeper issue in that when you have LD which accepts programs that cannot be proven to be error-free, you will tend to write such programs all the time; in fact, write mostly such programs! Therefore the diagnostic "I cannot prove that this program is free of type errors" will be noted, but ignored by the users of the language. Advocates of static typing want to modify other people's behavior so that they do not write such programs, and do not ignore that diagnostic. This is a sign of some psychological illness: identifying some mathematical subset, such as programs which cannot be proven to be type-error-free as "supreme evil" and wanting to control other people's behavior with respect to this evil.
Illness? I don't think so. Perhaps there are some out there who think that DynamicTyping is evil and should be banned... but I'm not in that camp. Both have their uses.
One reason that the diagnostic is easily ignored is that it carries no information which the programmer didn't know already. Suggesting specific spots where declarations might be added would be far more useful.
There must be some advantages of dynamic type checking. What in the world are they?
If you don't have it, you will badly reinvent it
Or worse, someone will
really badly reinvent it, and you will have to work with it.
See
GreenspunsTenthRuleOfProgramming,
ExampleOfGreenspunsTenthAtWork.
The programming language is simplified
Static typing adds to the complexity of the programming language. When you define
a new language, do you want to focus your effort on the type system or on giving the
programmer the functionality he needs? (Sometimes a static type system makes a language harder to extend, as Java and generic classes.)
It can help with refactoring:
- As someone who has only become enlightened recently (and still immersed in a statically-typed world), I cannot be too entirely specific. I have noticed that I waste a lot of time when refactoring fiddling with the types of things. I'm a greenhorn at this, and would be overjoyed to see this comment replaced with some more solid answers. (One example can be found in the first half of KentBeck's book TestDrivenDevelopment. A sizable portion of the refactoring he goes through in the money example is due to Java's typing. -- BilKleb)
This is more about the lack of static typing, not the addition of dynamic typing. A lack of static typing can help you to refactor incorrectly! Suppose your refactored design requires something as simple as an extra method in one interface. Without static typing, you would modify each instance of client code so that it called the new method. You would then need to modify each object that might be used by that client. Which objects are they? Oh dear... With static typing, add the new method to the interface. All implementors will not compile until you've provided them with an implementation of the new method. It's that easy!
But UnitTests, if used, will also catch the (generally small number of) problems that static compilation will catch, in addition to catching logic and other errors. My experience with a DynamicTyping language (Smalltalk) is that a very small percentage of run-time errors are due to mismatched types... maybe 1 or 2%.
It can help you develop with less distractions:
- The problem with types is that they force you to say things that you don't know are true, just to get the compiler to bless your program. "Here is the interface between these two objects" is a good example of the kind of thing you are forced to say before you know it's true. The interface between two objects invariably changes. I like not having to specify it when it is sure to change. Instead, I can say "This object needs an object like that in order to work". I know this is true (for now) because the code demands it. When the code changes and the interface changes, I change the code (which I know needs to change), but I don't have to change anything else. -- KentBeck, on ExtremeProgrammingWithTypes
But strong static typing can help you run your program without so many error message distractions that which some would have been caught at compile time. Although, I can see that many won't be caught at compile time, and one disadvantage of the compiler moaning is it causes code to become more full of casts or hacks to escape the type system. Consider though, that languages with strong/static typing such as freepascal/delphi have many dynamic emergency features such as Variants and Array of Const (pass as many different types into a single procedure or method as parameters). I consider languages that give you an escape system, just in case you need it, compromises... maybe a better compromise than always having just dynamic types. Another way to emulate dynamic typing is through a struct or a record with an enumeration pointing to the kind of type currently set. It requires more work than a truly dynamic typed language or a built in Variant type (which is a dynamic type in fpc/delphi), but the record trick works when I need it.
- I find that the longer compile times cancel out the time saved by catching stuff earlier in the process. Also, "suspicious code checkers", similar to C's "Lint" utility, can be employed with dynamic languages to catch many silly typos. This, it is not necessarily an either/or argument.
But on the other hand, you have to manually remember which things
do need to be changed. A static type system is a way of automatically being reminded of such things. Why do things manually?
You can have a polymorphic interface without "defining an interface":
- This is what happens when you realize, "oh, so this would work with any class that supports the 'print' method." If two classes have method(s) with compatible signatures and semantics, then you can use one in place of the other - without having to define a formal interface class and change both classes to "implement" it. This can be a good thing if you can't or don't want to change one of the classes, or if the classes don't have a convenient common ancestor.
You mean StructuralSubtyping? That's orthogonal to dynamic typing: it may be most famous as a feature of things like PythonLanguage and RubyLanguage, but it is also available in purely statically typed languages like ObjectiveCaml.
But templates/generics do the same thing, without sacrificing the compile time check to ensure that all usages
do have the correctly named method available to them.
Depends on the implementation of generics. C++ templates let you do whatever you want; errors are only detected when a template is instantiated and the compiler notices that the type being used doesn't support a specific operation requested of it. EiffelLanguage, on the other hand, provides bounded polymorphism - by specifying a base type that all type parameters must subtype, but prevents you from any operations not defined on that base type.
Sacrificing? This check can still be made in a dynamically-typed language, it's only that the compiler may or may not have sufficient information to produce a definitive answer.
Compilations go much faster
- This becomes more true as projects get larger. With a statically typed language every class that gets compiled must find things out about every class it derives from, every class it uses for member variables and every class used for locals and parameters. With dynamic typing, all the compiler cares about is super classes. When dependency chains start getting long compile times increase dramatically.
- But take note that statically strongly typed freepascal and delphi compilers are faster than nearly any compiler in the world, and produce fast programs according to shootouts and benchmarks. Compile time can depend on the module reliance.. as each module can be compiled separately.
- Indeed; Haskell takes forever to compile software, compared even to GCC; but, of that time, type inferencing and verification is but a fraction of a second. Most of the work the compiler is doing is code transformations after it's already proven type safety. --SamuelFalvo
But that just defers the work to runtime which degrades performance. What's worse is that while the compiler has to do this work once, the runtime has to keep doing it over and over again. Sure, the runtime can crib notes to make it go faster on successive calls, but it's never as fast as a compiler can make it (is it?). What would be really great is if we had fast dynamic typing style compiles during development and then we could switch over and do a statically typed build just before deployment.
You can already do this in dynamic languages which support optional type declarations. CommonLisp and DylanLanguage, for example, do this.
(Well, I kind of like the way the Dolphin tutorial workspace invites you to compute 200!
...)
Re: "compiler has to do this work once". Not during debugging and testing. One may have to recompile many times.
The compiler generally keeps the compiled pieces of the program in memory though. So compilations do go fast. Usually, you just compiled the unit or file with the changed source code.. not the entire program.
It helps you better apply the OnceAndOnlyOnce principle:
But without static typing, each time you implement an interface, you are providing a redundant "definition" of it.
'''How is that different from static typing, where you declare conformance to an interface, and then code
a redundant definition of it that supplies the method bodies?'''
Each time you call through the interface from client code, you are adding more redundant (but probably partial) definitions of the interface.
Calls are not definitions; and anyway they are manifested in static and dynamic code.
So where is the single definition of what the interface actually is? Nowhere!
A single interface definition, to which all implementors and clients refer, is a
perfect example of the
OnceAndOnlyOnce principle. (Note: this is something that is missing from c++ templates, because they are unbounded, but in practice it isn't a problem because everything gets checked at compile time anyway.)
It can allow a simplification on the users' side at the expense of a little wizardry on the implementation's side:
- Typical Smalltalk editing environments can more easily integrate spiffy features. For example, near-instantaneous compilation to bytecodes, because the compiler doesn't need to make sure that each method call is valid. When you change an interface, nothing else in the system needs to be recompiled.
But on the other hand, a single definition of a given interface will tell the compiler which things need to be recompiled. If you edit the interface, then you should have changed all clients/implementors accordingly - so the compiler checks for you. This works most optimally when interfaces are minimal (one or two methods each.) A given object will likely implement several such interfaces to build up a useful feature set. The Java standard library has many elegant examples of this.
- The Perl analog of yacc, "Parse::RecDescent", accepts a grammar definition in the form of a string and generates classes for each of your rules as it parses.
- That's eminently typeable in a properly dependent typed language, such as Cayenne or Epigram. It could also be done in Haskell, Tim Sheard's Omega or DependentMl? if the the string was replaced by a suitable PhantomType?. Once you start to do really complicated things in this area, dependent types could potentially save effort because they can rule out behaviours that are difficult to eliminate by unit testing. --RobertFurber?
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. This is more or less unfortunate depending on what the type system allows. Take reading from a file for an example of where type systems get in the way. There are two normal events that can occur when reading from a file: you get some data (say, a character) back or you get to the end of the file. In a dynamically typed language you can either return a character or a special EOF symbol to indicate these two events. 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).
When I first read "file" I thought you were going to have a much better example. Here's the statically typed
HaskellLanguage answer Maybe Char. And the characterization of "most statically typed languages" is obviously false, even C returns a "special EOF symbol" rather than chicanery you describe. Furthermore,
CeeLanguage and
JavaLanguage are not the best examples of static typing (they may well be some of the worse). Standard Haskell's type system is decidable and almost completely reconstructible (inferable), with extensions you can have more power but (potential) undecidability, and finally you can always use/simulate dynamic typing when your type system fails. So while the point is true, I don't see it as much of a point. -- Darius
A fascinating article on the topic, "Why Dynamic Typing":
http://www.chimu.com/publications/short/whyDynamicTyping.html
For the opposite question, see:
WhenIsManifestTypingConsideredaGoodThing
Since I first read this stuff (a week or so ago) I've had some refactoring work to do, and as I was doing it, I continually found reasons to thank my development environment for being very strongly type safe at compile time. I would estimate that it saved me hours and enabled me to make some bold changes, secure in the knowledge that the compiler would identify all the other necessary changes that resulted from them. The original list of points makes me think of someone saying "I don't have to bother going to the toilet. I can just sit here and crap in my pants. It saves a whole lot of effort!" --
DanielEarwicker
''OK, so you do not want to crap in your pants. Does that mean that you need some mechanism to
prevent you from doing so, or do you rely on your own skill and judgement?''
Note that 99% of the animal kingdom gets by rather nicely with this rules. Indeed, during 99% of human history, this was the case. Perhaps YAGNI still applies. --
StephanHouben
99% of animals crap on themselves? It's truer to say that most take a lot of trouble to do it somewhere out of the way from where they have to live. YouAintGonnaNeedToilets
?? I hope we can leave that as a personal decision for each
WikiWiki citizen to make for themselves! It's up to you!
I would estimate that it saved me hours and enabled me to make some bold changes, secure in the knowledge that the compiler would identify all the other necessary changes that resulted from them.
Do you have
UnitTests? If you took the static typing away, would they catch these errors? If not, what do you do when you make non-type-related changes?
I do have UnitTests. The only really sure way to know that they would catch all the errors that the static type system catches is to perform a thorough static analysis of them... which is exactly what a static type system does for me! Do you know if your UnitTests are a complete validation of type-related mismatches in your code? If you have no static type system, the answer is either "no" or "yes, because my program is very short and simple." Are you trying to sell me the idea of manually hand coding UnitTests instead of using static typing to take care of that part for me? Thanks, but no thanks... A static type system is just a way of automating the parts of UnitTest coding that it is theoretically possible to automate. The remainder has to be hand coded as UnitTests, but why add to that burden? The two techniques fit together - see UnificationOfStaticTypesAndUnitTests.
You've divided up the
UnitTest universe into Type Tests and Behaviour Tests. Under what conditions would the Behaviour Tests alone not be sufficient? If the behaviour is correct, how could the types be incorrect?
I've never seen anyone write Type Tests in a dynamically typed language, and I've never seen a case where they would have been of any use.
Obviously if you have a complete set of behaviour tests, then you don't need type tests. But of course, if the behaviour is perfect, you don't need behaviour tests either. These things are redundant mechanisms for checking correctness in whatever you're doing. Any harm in adding another one? Type safety really honestly doesn't waste any time or effort, it saves it it bucketloads. And it checks the type-correctness of your UnitTest code as well, so it speeds up the writing of correct UnitTests.
If the behaviour is perfect, you still need tests so that when you change the behaviour, you can make sure it's still perfect.
Redundancy has diminishing returns. What percentage of errors do you suppose are type errors? What percentage of errors do
UnitTests typically catch?
You just said, "Static typing has no cost, and it helps, so why not use it?" That's the question that all those reasons at the top of the page are trying to answer. Static typing
does have a cost.
A salient point. Solutions have costs; when dealing with costs, as KentBeck stated in XPE, the best thing to have is options
. Familiarity with the BenefitsOfDynamicTyping is one way to have options in this case. Contrasting Java and Smalltalk - the Chimu article referenced above appears to do that in an interesting way, though to be honest I have only skimmed it yet - could be a good way too. Another way that might be interesting - what if the polarized participants on this page switched roles and tried to argue the opposite point of view ? (I'm being idealistic now...)
I'm willing to try that. How about a
BizarroStaticTypingDebate?
I can't see how a statically typed language can force you to use its type system. Watch a rookie C++ programmer at work! They have no problem getting round it. It's always optional. A programmer always has the option to sling everything into an associative array, and I do that where dynamic extensibility is the main requirement. But there's a pattern in my experience: whenever a rookie programmer in the place I work "side steps" the type system, the result is a disgusting design! One minute you have a beautiful polymorphic system, the next minute, someone has added a "type field" to the class that has to be checked everywhere to handle special cases...
Don't confuse the typing axes
manifest--latent with
weak--strong.
LispLanguage is
strongly typed, but with
latent types. C++ is
weakly typed, but
manifest. That's why you can cast a pointer to any type you like. Whether or not it'll work depends on the semantic of your program. You can't change the type of a Lisp object - all objects know their types. (Forget
change-class for now - that's a different issue).
My experiences as a recent
DynamicTyping convert:
I was trying to implement a really complex algorithm in Java. (If you want to know, it's a seriously tweaked MarkovChainer
?, which alters how it looks up the next word depending on how common the previous words are in the input text.) I couldn't do it. The domain was pretty unknown to me, so I was hoping I could implement the simplest version and then slowly refactor my way into the really subtle stuff. But every little refactoring seemed to involve me touching eight classes, just changing types in method signatures, and the slowness of it - combined with the
EssentialComplexity of the problem - made it pretty much impossible. I gave up.
Months later, I decided to try coding the thing in
RubyLanguage. (I believe that you can't really learn a language until you do something difficult with it.) And things moved a lot faster. I would focus my attention on just one class, messing around with interfaces without worrying about the interactions between the classes. I just worried about how that one class talked to its own
UnitTests.
This is impossible in a statically typed language like Java - change the method signature in one class and you have to go in and change every other class that uses it just to get the thing to compile. But with Ruby I could try 20 different variations of (say) T
upleMaker, not worrying about how I was breaking M
arkovChainer, W
ordRanker, etc. for the time being. Then when that T
upleMaker was settled down, I could run all the
UnitTests, and hunt down the 30 or so errors to change the client classes where needed.
You said with java, when you change one class, you couldn't get other class to compile. And you have no problem with Ruby because it doesn't tell you that. But, in fact, all other ruby class of you still can NOT run. So is there any different from java remind you of what you haven't corrected and Ruby let you go on and program die if you run it? When you work with Ruby You only work with the class you are modifying and its UnitTest. So why not do that with java. Why did you compile other class if you don't use it? you don't have to always use javac *.java. you can choose to compile just some classes. But you compile other classes to be SURE that every thing known of change of other classes it involves with. Isn't that good?
The Java compiler will flag a lot of things as errors. Not all of these are actually problems with the program. Many of them will simply be hoops you need to jump through to assure the compiler's type system. Secondly, the compiler's blessing in no way guarantees that the program is semantically correct. It only guarantees that you're not breaking the somewhat arbitrary rules of the type system. You still need to test it.
It isn't impossible; rather, you need to use a good Java refactoring tool (e.g., Eclipse) that allows you to easily change all references in a heartbeat. I started off in the 70s with dynamically typed languages: I worked as an APL developer. Then lots of Lisp, Prolog, Focus, and Smalltalk in the 80s. However, with the introduction of really powerful refactorings, and code templates in Eclipse or IDEA for Java, I now seldom miss dynamically typed languages. That said, I think Python and Ruby very fine languages. -- CraigLarman
I'm a big believer that
ContinuousRefactoring is a very good way to find the right solution, and that the need for discovery-through-refactoring increases as the problem gets more complex. But you need a language that lets you refactor continuously, and dynamic typing seems essential to that.
Interesting corollary: Before I started using
UnitTests and other XP practices, I didn't have to worry about the hard stuff nearly as much, since I was spending so much more time on the easy stuff. Perhaps it's a sign of success when much of your programming time is spent on hard stuff - it shows that your skills of reuse are getting better. -- francis
-
- A good editor can help immensely with this as well, however. I'm no huge fan of static typing, but having an editor which can rename methods, change parameters/parameter types, create stubs, etc., is a tremendous help when dealing with static types... come to think of it, what would it be like if one could have an editor which could hide all the static typing details? You'd probably still have to deal with types as far as using libraries, but internally, shouldn't it be possible to create interfaces, methods, etc behind the scenes in response to a new unexpected method call? --WilliamUnderwood (younger)
It seems to me that static/dynamic typing is primarily a user-interface issue. When you _want_ to specify types it's annoying to not have that safety-net. But when you don't care about types, working with a static type system is stifling. In either situation, a good programming environment will help a lot.
EdGroth
-
- I'm almost of the opinion now that it's actually the opposite: you use static typing when you _don't_ care about types; you just let the computer deal with it. When you use dynamic typing, you're taking over responsibility for these issues from the compiler. In a concrete sense it's a question of indirection, in that a dynamically typed system will always have at least one layer of indirection more than the equivalent statically typed system. A smart runtime might be able to perform some magic to move the cost of that indirection (Jit, optimizing based on accessibility, etc.), but the cost is always there. -- WilliamUnderwood (older, wiser?)
Shouldn't we make a distinction between dynamic typing and type-free languages? Type-free languages do not carry any internal type indicators/flags along with variables. Their values are more or less just strings as far as the language is concerned. I prefer type-free over dynamic typing.
Doesn't sound like that useful of a distinction to me; it sounds more like implementation details. Whether an object is represented by a bunch of bits and a vtable, or by a string--some operations are supported by some objects and another. You can assign meaning to the expression "5" * "4"--most folks will assume that this is "20". Multiplying "cat" with "foo" is another matter. Ignoring bizarre languages which do define such multiplication; attempting to multiply two strings in this fashion will either result in an error or UndefinedBehavior (preferably the former). Even in the absence of a declarative type system and type annotations, there is a clear difference between those objects which can be multiplied together, and those which cannot. And that distinction is an example of a type.
{So any distinction could be called a "type". That is just a label.}
The one gotcha you have to be aware of is using the right kind of compares. This is why Perl, for example, has "==" for numbers and "eq" for strings. Personally, I wouldn't use that particular syntax if given a choice, but the concept is there.
If you have to worry about how to spell the equality operator - that's another type distinction.
{Typos are typos. What is your point? It is better documentation also. You don't have to hunt in the declarations to see what will happen. Further, a database or remote service could change the type of an ID from numeric to string, for example, without resulting in recompiling our code. Plus, they make this kind of thing less ambiguous:}
a = 5
b = "foo"
if a > b ....
versus
if a s:> b
Where "s:" means compare as string. We can also add other letters for optional case sensitivity control and for ignoring leading and trailing white spaces. It is hard to cram all that into the concept of "types".
......
As far as why I prefer type-free over static typing, it is because it simplifies the code, and I believe
ThereAreNoTypes.
The same can be said for dynamic typing as compared to static typing (or more correctly, ManifestTyping).
Types are too artificial, especially for dynamically changing things. They make you spend too much time trying to force the world into taxonomies which are not really there.
But there are taxonomies. There will always be taxonomies - and non-artificial ones, at that. They might not be hierarchies - and hierarchical type systems are limiting, I'll agree. But taxonomies do exist, whether you like it or not.
{Yes, but they tend to be situation-specific and fragile. Global taxonomies are nearly non-existent in real-world things, especially those created by humans as opposed to natural laws. And, creating local in-code micro-taxonomies for everything is a waste of code and time. Typing tends to assume some grand, global, stable classification for stuff, and this is a not the case. Why hard-wire your code around something so fragile, situational, and temporal? It might be livable, and arguably beneficial, for
simple base type concepts like numbers and strings, but it does not extrapolate well to more complex and dynamic things. Types take too much set-up work. If they are generally only relative and temporal, then that set-up work is not justified.}
Related to "type-free":
PowerOfPlainText
In my (limited) experience,
DynamicTyping means more typing - on the keyboard - from a software engineering point of view.
Imagine a classic static SQRT function:
double sqrt(double x)
{
return ...; // implementation irrelevant
}
What's the general design contract of this method?
// in: a double
// out: a double
// semantics: square root
This static version validates the in and the out part. Validating the semantics is up to the unit tests.
Dynamic version (pseudocode of course):
sqrt(x)
{
if (x instanceof Double)
{
return ...; // implementation irrelevant
}
throw new TypeMismatch?("Parameter x to sqrt must be Double");
}
Clearly, to implement the same contract in the same strength, a lot more typing is needed. Of course
DynamicTyping people don't write the typecheck, meaning they don't respect design contracts. Which leads to obscure errors coming from deep inside your program (doesNotUnderstand anyone?), breaking apart implementation hiding.
This is very easy to reason about. The set of dynamically typed programs is a straightforward superset of the set of statically typed programs.
But there's also a simple transformation from a dynamically typed program to a statically typed one: Introduce a single type Univ which is a tagged union of all primitive types, extend all operations to operations over Univ, tag all literals with their type. All but some of the tagging would be done in a library and thus be essentially invisible.
In that sense, the set of dynamically typed programs is a strict subset of the set of statically typed programs. Actually, dynamic typing is static typing with only one type, therefore is no typing at all. Maybe there are also other, true benefits?
To be more correct about it: Dynamically typed programs are a trivial case of static typing, where all terms are assigned the same (universal) type, and certain operations are checked at runtime for correctness. It's not hard to implement a dynamic typing framework in a statically-typed language--you can even do it in C. Of course, the typechecks at runtime are what static typing fans object to; both for performance and correctness reasons (primarily the latter).
By introducing more complicated type systems, it is possible to reduce the number of runtime typechecks, by proving that a given term always has a specific type (or its subtypes). The ideal is to eliminate all runtime typechecks; indeed, many statically-typed languages disallow dynamic typechecks (and perform TypeErasure, making them impossible anyway).
However. There are programs (including real-world ones) and useful langauge features, for which static typing is not possible. Static typing zealots seem to think that such programs/features should be avoided completelty. Dynamic typing zealots seem to think that this is cause to throw baby out with bathwater, and abandon static type systems, under the logic that if they can't be perfect; they're useless.
Needless to say, both camps of extremists annoy me. :) -- ScottJohnson
After reading all that, I'm noticing a theme that doesn't seem to be explicitly stated. The difference between
StaticTyping and
DynamicTyping, is not that they do anything substantively different, its merely a difference in where and when the type checking is done. They're both typing, are they not? There are a few arguing that
ThereAreNoTypes, this is not the same thing as
DynamicTyping. Calling it
DynamicTyping implies types exist, does it not?
Many seem to use "typeless" and "dynamic" typing kind of interchangably, or at least consider no typing part of dynamicness because it is often context-based in a conceptual (human viewpoint) sense. (Related: TypelessVsDynamic)
Anyway, in a DynamicTyped
? language, the responsibilities of any desired type checking is moved to the programmer, and this implies that its done at runtime. As shown above, this results in code being put into every class to check type safety, but not only that, since its all done at runtime, it is now the responsibility of
UnitTests to check that type checking is being done where desired. This is a bunch of additional code when type checking is desired.
In a StaticTyped
? language, the compiler does type checking , as well as make optimization decisions based on the type. People argue however that this limits you. Does it, really? In Java, you can take and return Object, and use reflection to "bypass" the compile time type system. And types can be converted from one to another with polymorphism. However this is a bunch of additional code when
DynamicTyping is wanted.
In summary, the real difference is
StaticTyping gives the compiler more information, thus allowing it to catch more errors, as well as make better decisions about optimization. The rest all just reeks of different flavors of
SyntacticSugar to me.
CategoryLanguageTyping