Why Dart Types Are Optional and Unsound
dartlang.org
dartlang.org
> We don't want Dart to be a testbed for active programming
> languages research, but I think we're open to taking up
> ideas after they become well understood and unsurprising,
> a.k.a. a little bit boring.
... after saying this in the first paragraph: > Dart uses types in a way that might seem strange. Most
> popular languages that offer a type system use it very
> differently. If you're familiar with types in Java, Haskell,
> Scala, or any statically typed language, you may wonder
> why on earth Dart makes the choices that it does.
... makes me wonder why on earth Dart makes the type system choices that it does ;)No, but you can generate better code if you know exactly what types you're playing with ahead of time.
Say you have a function like this:
void foo(SomeClass obj) {
obj.bar();
}
That could be compiled to just a straight 'jmp SomeClass::bar', whether you're compiling natively or to a VM.If you don't have static types, the best code you can output is something along the lines of this:
fn = lookup(obj, "bar")
jmp fn
Of course there are tricks that you can use to optimize this second case a bit that involve fancy static analysis, fancy JIT business, etc. but the point is that you will never generate better code than just a single jump to the right code, based on the types you know at compile time in a static language. > Correctness is easier to achieve for a VM that doesn't
> rely on static soundness guarantees, because you don't
> need the complexity of bytecode verification.
I don't follow. You only need to verify bytecode if a), you have bytecode in the first place, and b), you assume the existence of a hostile compiler or other external bytecode source. Neither of these apply to Dart.By being higher level Dart can work from a subset, keeping the surface area smaller as a result.
All in all, you should run in checked mode for more feedback. The idea is that you get warnings when stuff is bad. When stuff is super bad you get runtime exception.
Dart is optimist and will give you warnings and let your code run until it cannot.
For one example: Haskell's strong static types all go away when you compile; the generated code does not do any type enforcement or verification.
The biggest thing I hoped the article would talk about got shot down in the very first section, which just said "Dart is dynamically typed" and explicitly dismissed any need to justify that, other than by saying "Users of dynamically typed languages ... will understand why we chose this".
I honestly can't even begin to understand why people consider dynamic typing a feature; when I saw the headline I hoped to see an interesting justification, but instead I saw it taken as a given and then used to justify many other things. I agree that almost all of those things do follow naturally from the assumption of dynamic typing, but I'd still like to know the rationale for that assumption.
The only justification I've ever seen for dynamic typing amounted to "because I like duck typing". However, a static type system can trivially enforce duck typing.
1. Better code documentation. Based on my experience with Python, reading someone else's code (or even my own code after a while) is harder than e.g. C or Java code because types do provide useful information. When reading Python function in isolation I often wander: is the argument to a function a list? a dictionary? a string? Knowing that makes a big difference when reading a code and when I don't know that it's hard to figure that out because you have to trace the whole control flow to that function. This is especially important in large projects where you can't keep all the code in your head and how to read small parts of it.
2. Tools can tell you about errors. Part of the Dart project are lint-like tools that check annotations and notify you about mistakes. If you e.g. annotate a function as taking integer but you pass string to it, a tool can tell you about such mistake. This is what statically typed compilers always do and, again, it's especially important on large projects where you're only responsible for part of the code. Dart doesn't force you to use type annotations but you can choose to use it in your project and get less bugs by using Dart code lint tool.
My tastes run towards a C#-like language (cough Gosu): statically typed with inference, simple generics (covariance), proper function types to fill in most of the covariant holes, and a dynamic type escape hatch, just in case.
But of course they would.
mammal = cow; // [1]
pig = mammal; // [2] Checks OK statically, but now pig holds a Cow.
print(pig.oink()); // [3] NoSuchMethodException if we get this far.
As far as I understand this means that no precondition can be encoded in the variable type. A function with a Pig argument can still cause a NoSuchMethodException exception because the argument can actually hold a Cow instance.> That's what I thought, too. The VM designers say that in practice, type guarantees really don't help them nearly as much as you might think, because type checks are not a major drain on performance.
If this is the case, what is supposed to make Dart faster than JavaScript? It sounds like they will end up around the same speed.
In JavaScript, I can write this:
var dog = new Animal();
dog.name = "Fido";
... adding a new field to the "dog" object, even though other objects of the same class may have no such field. Objects that share common sets of fields are the famous "hidden classes" that V8 is able to optimize.You can't write the above example in Dart. Dart doesn't have to work as hard as V8 to discover and cache hidden classes, because the shape of an object is statically fixed to be the same as its class.
Take a dynamically typed language, say, Python:
a = 1
a = "foo"
What disadvantage would you have if the language used type inference to set a to a static type of int when it encountered it?
a = 1
a = "foo" # compile/interpretation time error, "a is an int"
1) You get the speed/optimizations + tooling of static typing. 2) You get the dynamic typing benefit of not messing with type definitions etc.
Do people really do stuff like changing the type of a variable in normal Python use, anyway? Usually when I set something to something, I keep it in the same type.
Is there a disadvantage, besides having to initialize every variable upfront? Polymorphism/Generics are orthogonal to this, no?
Are there any languages that do it this way?
There's nothing wrong with that, but what it does mean is that people are much more likely to simply use classes and inheritance trees for everything, to simplify the syntax. Laziness will win out often enough to make writing / finding good code a nightmare.
edit: also, if you try to infer polymorphic changes / interface changes, then it gets harder and harder to figure out what's correct and what's not. Was the change earlier dropping your collection to an enumerable correct, or was it the hash you just tried to save? And what if you are interface or inheritance-happy, and have hundreds of possible options? It can probably be solved reasonably quickly, but not for free.
There are some common language features that make type inference tough, most notably mutable data. It's difficult to infer the element type of an array unless you know where it's been, which can be difficult or impossible to know in situations involving separate compilation. Some ML variants can infer all types for programs that avoid using mutable data.
In other words people do use these features, they use these features a lot and that is not a problem. You are just using an overly trivial example in which you make assumptions without considering all the consequences.
Why would they be? You just need a common parent type, like object, or something like Objective-C's "id" type, to indicate that a structure/method can accept any type.
So:
a = 1 # int
b = "foo" # string
c = Cat() # cat object
d = Singer() #singer object
you could have a built-in, say list, structure:
mylist = [a, b, c]; mylist.append(d)
or
myadt = CustomDataType()
myadt.add(c); myadt.add(d);
"""duck typing doesn't work anymore etc."""
Wikipedia: "duck typing is a style of dynamic typing in which an object's current set of methods and properties determines the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface".
So, why would duck typing not work? Duck typing is about the ability to call methods on any type that implements them. It's irrelevant if the type is static, not? Say:
foreach item in myadt: item.sound()
Cat and Singer could be from totally different objects hierarchies, and the compiler/language would know that both implement "sound()" and let you call it.
If an object on myadt doesn't implement sound() you get a runtime error.
"""You are just using an overly trivial example in which you make assumptions without considering all the consequences."""
Emm, the whole point of my comment was in me EXPLICITLY ASKING for possible consequences. Which you kinda missed, judging by this statement.