Transcription of A Quick Tour of Dart by Gilad Bracha
On November 2nd, 2011, Gilad Bracha gave a talk at Stanford University titled "A Walk on the Dart Side: A Quick Tour of Dart". I transcribed nearly all of the video so you can more easily skim and study the material. Thanks to Dart News for pushing to YouTube. Learn more about the Dart language.
(disclaimer, I did my best to capture what was said, any errors are my own)
(disclaimer, I did my best to capture what was said, any errors are my own)
- We’re talking about dart. It’s a new language we’re working on at Google. It’s really not my work. I joined the team the 3 months ago, but this project has been running for about a year. I’ve just started to contribute to this in any way I can. Overall, it’s the work of the Dart team, which is a fairly significant team.
- At 50,000 ft, the overall summary is that it’s a language for programming the web. What is special about the web? In principle there’s nothing special about the web, but in practice there’s a lot that’s special about the web. And we’ve had an arguably had a regression in terms of tooling and the overall environment that we use to program the web applications in the browser. The fact is that making a significant application that runs in the browser that is competitive with what are considered good applications that are written natively is a very very challenging thing. This happens once in a while but it’s much harder and people jump through a lot of hoops. We want to make it easier.
- The actually technical summary of what Dart is: an object oriented, optionally typed, class based, single inheritance language with actor-based concurrency of some form. So you can yawn now and say, “What’s so special about that, that’s done a lot of times before.” Largely that’s true, though some of these things haven’t hit the mainstream yet in very wide usages. We hope to do our part to bring our technologies to a very wide audience. The one thing that’s a bit unusual is this business about optionally typed. It’s also gotten people all fired up because there’s nothing better than a good religious argument about typing to keep people interested. The only thing better is to argue about syntax, which is even more fun!
- Optional types are probably the main thing I’ll be talking about. I’ll also be talking about supporting abstract data types without types or dynamically typed languages as it were, which is slightly interesting, and built-in factory support. All of things are what’s modestly new in Dart that hasn’t been as widely circulated as the rest, by and large. The language is designed as I said to be familiar and unsurprising.
- Optional types. What do we mean? First of all, a bit of terminology. Optional types as opposed to mandatory types. Mandatory types are the types you know and love or hate as the case may be in most “statically typed” programming languages. The idea is that the type’s are required, and a legal program has to pass the type checker, otherwise it can’t compile and it can’t run. There are many examples of that: . By no means intending to disparage them, they have a philosophy about types. But you can read the color scheme as “leaving the tyranny of type theory behind and into the sun lit plains of the wild west and optional typing.” Depending on your religion, of course. :)
- So, we don’t want this (mandatory types). Both for our own experience, I’ve worked with both statically typed and dynamically typed languages and I have a preference but much more important the web is already used to dynamically typed languages. For better or for worse, you can argue about it but that’s where our demographic is.
- Brief history of non-mandatory types: there’s been many many years on this idea. Many people want to have the advantage of static typing but don’t want the disadvantages. I’m not doing an academic talk so not spending a lot of time on prior work, but here are some examples: Common Lisp, Scheme, Cecil, Erlang, Strongtalk, BabyJ. As usual, it’s all been done first in Lisp, I’m not even going to contest that. The Scheme people did an approach called soft typing, perhaps the most academic work. Strongtalk was a language I was involved with many years ago, worked with Lars Bak, who is perhaps the father of Dart, as it were. Strongtalk was significant perhaps more for the engineering work done, which eventually became the precursor to HotSpot. It had lots of other innovations at the time, including an optional type system. The one that I’m interested in, of course, is the one I worked on (Strongtalk). Of course, how could it be different. :) I some ways it’s the precursor to the work we’re doing on Dart.
- Optional Types: The idea of optional types as we see them is A) of course they are syntactically optional, Everyone gets that. The important thing is that they don’t affect the run-time semantics. This is a surprisingly difficult thing to grasp, because every language that does have mandatory types somehow or another the actual run-time behavior starts to depend on what those types were. Which is very different from say a lambda calculus where the thing works just fine with or without, or works better without, the types. Types don’t change anything. They tell you something might be a problem but they don’t change the reduction rules or anything. So we’re more in that spirit.
- What does it look like? (shows Point example on Dartboard) This is Dart running in the browser. It should be fairly obvious what this thing does. It has instance variables X and Y. It has an overloaded plus operator that let’s you add points. There’s a main function which is the entry point. This is a completely dynamically typed program, you’ll notice that there aren’t any types here. There are constructors that are creating Points, and you can notice some sugar here with immediately taking args and assigning them to the corresponding fields. We can run this program and sure enough it runs. What’s unusual is we can start to put in types here (changes var to num). Nothing has really changed, and now we have a partially typed program. It doesn’t matter how many types we put in, it works the same. These things (the types) are annotations. Our purpose with types is largely documentation for the humans who read the programs and for the tools that inspect the program. People love name completion and various features like that in the development environment, and it’s a lot easier to provide those features if we provide the machine with this information. And we’re not hung up if that particular information is right or wrong, which is the strange thing. So if for example I was to say this (some point) was a string, which is nonsense, it can still do it. (runs program, it works!) You don’t know how quite evil this gets. (changes return type of scale to be string. program runs but generates one warning) Changing the types here doesn’t change the semantics, and that’s on purpose. The idea here is that we have a large body of programmers who haven’t seen a type in their life and are in no hurry to meet one. They should be able to ignore typing all together. On the other hand, we should be able to give people some meaningful messages about errors, name completion, so there’s positive value with these declarations. From my experience, most of the value is in having the documentation as opposed to the common approach of saying it’s great for detecting errors. A good programming environment will detect these errors early on. I don’t generally believe the assertion that there’s a difference in robustness in these languages.
- Mandatory Types: pros. So far you’ve seen it’s a very vanilla looking language, except for this strange business of the types that give you messages but don’t actually stop the program from running. The order of importance for mandatory types: machine checkable docs, types provide conceptual framework, early error detection, and performance advantages. Never the less, we’re now at a point where we can get good performance from completely untyped code, and for the kinds of applications we’re seeing on the web, it’s not particularly critical. Early error detection is what everyone talks about, but it’s not entirely crucial. What is crucial is machine checkable docs. Types also give you a useful mental framework. These are the things that are actually important to us.
- Mandatory Types: Cons. What’s less talked about is the downsides of types. This usually deteriorates to a religious war of one kind or another. There are things that you find difficult to express with a particular type system. If you build a type system that lets you express that, it’s lovely but the system is some complex that only a few logicians are comfortable dealing with it. There are many people that are quite happy to live in a world that a given type system provides them, but we’re not among those. I can give a few examples that would be handy to do engineering wise that are difficult to express. Another issue is that type languages tend to impose a work flow, they tend to make you work in a certain order. For example, “things are missing, things are undeclared, things are inconsistent and please make them consistent before you proceed.” On the other hand, we find it’s much more fun to work with something that is less constrained. Starting with old “read eval print” loop, to environments where you run something, the debugger pops up, and then you fill in the missing method. You build your program from the outside in. You can write a test, there’s nothing there, it immediately crashes, and it doesn’t immediately force you to go back to the beginning. It lets you incrementally interactively build your program. People who have worked with these environments get addicted to them, and people who haven’t don’t seem to get it. We don’t want a language that imposes a work flow. There is an issue with the birttleness of mandatory typing. There is an overwhelming temptation to rely on it for everything, for security, for optimization, and the experience I had on the Java platform where everything relies on that thin layer of typing and that thing collapses and it doesn’t actually work. That’s a different talk, but the point is that there are downsides to traditional typing.
- Optional Types: Can we have our Cacke and Eat it Too? Optional types are a kind of “have your cake and eat it too” approach, at least to some degree. We get the documentation, and we admit that the documentation might not actually be correct because we are no longer necessarily in a position to prove it. We still get the conceptual framework, and we often do get the early error detection (and again not fully guaranteed). Sometimes we can even squeeze from performance from these things because sometimes it easier to prove something given these annotations than to infer them and try to optimize. That’s really very much attenuated and not our focus.
- Optional Types Precludes... There are a lot of things you can’t do with optional types, there are certain language constructs you can’t have. For example, type based overloading in Java (what you actually call depends on the static types that you wrote). We can’t decide how things should be initialized based on the type information. For example, you can’t just write “int i” and we can’t initialize it to zero for you. We can’t do typed classes, like Haskell, one of the few examples of a construct that I’d want to have if I had types. Most of the others I’m very happy to dispense with. It is a sort of austere discipline.
- So what’s actually new? So at this point, somebody might ask, “Ok, this is fine, but what’s actually new? Didn’t you do this 18 years ago?” To a degree we did, but not many people paid attention. There’s always a gap between research/startups and hitting the mainstream and we think we’re ready for the mainstream.
- Type Assertion Support. This is really a type assertion system. We support a mode where we can use these modes to drive assertions in what we call “checked mode” so we can find out dynamically if some of these assertions are being violated. This is a very pragmatic thing, something we didn’t have before. This is really the way to think about this. The other thing is, for various reasons, the type system is deliberately unsound. This of course drives some people to high levels of anger, but they just need to view this as something else. This is not a conventional static type system.
- Dart Types at Runtime. Checked mode lets you interpret type annotations as adding assertions. T x = o ======> assert(o === null || o is T) This is intended for development, not production because it’s too expensive. Every time you transfer a value, pass a parameter, assign, return from a method, we do this check. This is handy for the developer because we localize the error quickly.
- (goes back to the point example, turns on checked mode in Dartboard, generated a “failed type check”) We’re driving this more by ergonomics than mathematical logic. As a practical matter, we’re not building this as a traditional type system. This is a tool in the service of a programmer and what works heuristically in practice most of the time in a dynamically typed language is what we’re concerned with rather than some abstract notion of mathematical correctness. Which btw you never get out of a type system anyway (you get a very partial notion of correctness). One finds that in these common situations that casting goes on. Remember the main audience is not used to casting, and will not fight this beast.
- Not your grandfather’s type system. It’s not a type system at all, rather a static analysis tool based on heuristics, coupled to a type assertion mechanism.
- What about a real, sound, type system? You can write a tool that will scream bloody murder about these things, but what you can’t do is stop people from running their programs.
- Runtime independent of type system. Traditionally, the kinds of types you can check depend on the types you created, but I find that the opposite which is what your program does depends on the type annotations is perverse. Decoupling the two means we can have the type system evolve independently of the language. If someone adds a new way to check types, we don’t have to rejigger everything because the execution engine doesn’t care.
- What about type inference? This question comes up a lot. People are very lazy, they hate typing stuff. They would like the system to do this for them and figure out what the types are. I’m happy for tools to do this, I’m not happy for the language to do this. What types you can infer depend on what types you have, but making the type system that you have depend on what you can infer means your type system won’t be good at certain things. We don’t want to restrict things.
- Don’t get boxed in. Execution -> Type checking -> Type inference. Where do you want to live? We’re trying to be very very forgiving.
- Interfaces. Interfaces are roughly what you would expect from Java. What’s different is that the type system works exclusively with interfaces. There are no types that tell you something is a particular implementation. Every class implicitly induces an interface, we use this to type check. Interfaces are reified at runtime. We use “is” which is like “instanceof”. You can implement the interface of another class without subclassing it. You may not want its code or implementation, but you want to easily write mocks to test it for example. This is what’s good about OO, IMO, in that you only depend on the externally visible interface, and as long as you can fake that behavior you can produce any kind of thing, whether that’s a proxy or a mock for testing.
- Generics. I can’t really give a talk about types without my long and tourtured history with generics. I’ve done generics several times, and maybe eventually we’ll get it right, who knows. (shows a simple generic collection, checking List<String>() is List<Object>, etc) This is one of the places that we and the theologians have parted company, because this is an unsound type system. List of string for our purposes is a List of object. Covarient generics, this is well known to be logically wrong. We can afford to have a rule that isn’t correct, because it’s a dynamic and pointer safe language. That means that in some cases we won’t be able to give you absolutely correct assurances about your program, but it also means we won’t have a programmer scratching his head over “what does contravarience mean?” So yes, a List<String> is a List<Object>, a List<Object> is NOT a List<String>, a List<String> is NOT a List<int>, a List<string is a List, and a List is a List<String>. These last two are interesting. What does it mean? It means that List is a list of Dynamic, which basically means “I don’t care about the type”. By covariance, because Dynamic happens to be a supertype of everything, a List<String> is a List of Dynamic. It isn’t necessarily safe, but we only want to present errors when we are very sure of errors, and only with relatively ground and concrete entities. We don’t want people reasoning about higher order things. So a List<String> is a List. Explain this to a guy on the street. If you can’t explain it at that level you have a problem. The last one is perhaps most puzzling, because you can certainly argue that not every List is a List<String> , but practicality has this nasty habit of introducing. We expect to have a lot of libraries with fully typed interfaces, we think that it’s a good thing, it gives people documentation. However what shall the programmer do when they pass a List without a particular type to a function that require a List<String> ? They will probably find out it requires strings and they will produce a List that contains strings. That’s generics in a nutshell. We’re trying something new.
- Optional Types and Reified Types. So you might ask, “you’re reifying these types. we actually have these type parameters for generics represented, so they do seem to affect something being executed. We reify the type arguments to constructors, and we also reify the interfaces, but they are optional. Under the covers we pass this magical Dynamic type and it’s accessed at runtime. BTW I have to emphasize that this is very early in the game. This is by no means a product, this a a very early stage of development and we put it out there for people to comments and complain and experiment and so forth.
- Summary: Optional Types. There is a static checker that provides warnings, there are no type errors and it is tuned to be unobtrusive. That is, it doesn’t try to catch every possible error. The types don’t affect the semantics. During development you can view this as a special kind of debugging mode where you can turn on these assertions.
- Some Modest Innovations. Abstract Data Types without types. Dart has this notion about libraries, it’s a very simple minded notion. A library is a collection of top-level classes, interfaces, and functions. They can import each other, they can refer to each other in a mutually recursive fashion, and they act as units of encapsulation. So essentially, privacy in Dart is something that following the conventions of these communities which they are quite comfortable with, even though you might question them. Privacy is based on names. Naming and privacy are not orthogonal and I can understand the objections to that. Basically if you prefix a name with an underscore, it’s private to the library in which it appears. This has some advantages, it’s context free. Anyone looking at this code can tell it’s private without going to its definition. On the other hand it’s not so pretty that I’m constrained on the naming. But what’s really interesting is how this relates to the interface based types system. (showing a code example on the slide)
- Factories. This is kind of nice. Constructors have well known problems. Constructors have failure of abstraction. With a classical constructor you are saying you want a new thing allocated. The problem is you don’t always want a new thing allocated. Maybe you want to look it up in a cache. Maybe you want to allocate it but with a different type. There are design patterns, but then people have to read the books and be more knowledgeable. So the idea is we support factory constructors, which we like to call constructors without tears. It reduces the need for things like dependency injection frameworks, not perhaps entirely. (demos example from Dartboard)
- Dart is not Done. Not by any means. Every known programming construct has been proposed on the mailing list, and most of them we had already looked at. We might do them later, we’ll see how things go. Mixins? Reflection. We have a very low level framework for actors. Looking at higher level things on top of that, like erlang style pattern matching, promise pipelining? We’re experimenting. Should classes nest? Should libraries be first class? Non-nullable types? Metadata? Pluggable types? There’s all sorts of things we might do or not do. We’re trying to get feedback from the community. And that’s about everything I had for you.
- Questions from audience (though hard to hear). We don’t want to over innovate. If you want adoption you can’t get too far ahead of where the wider programming public is. People who tie their performance to types have had disappointing results generally with dynamically typed languages. Optimizing performance based on types isn’t very high on the list.