1.2 Design Principles
1.2.1 Everything Is an Object
Dart is a pure object-oriented language. That means that all values a Dart program manipulates at run time are objects—even elementary data such as numbers and Booleans. There are no exceptions.
Insisting on uniform treatment of all data simplifies matters for all those involved with the language: designers, implementors and most importantly, users.
For example, collection classes can be used for all kinds of data, and no one has to be concerned with questions of autoboxing and unboxing. Such low-level details have nothing to do with the problems programmers are trying to solve in their applications; a key role of a programming language is to relieve developers of such cognitive load.
Perhaps surprisingly, adopting a uniform object model also eases the task of the system implementor.
1.2.2 Program to an Interface, not an Implementation
The idea that what matters about an object is how it behaves rather than how it is implemented is the central tenet of object-oriented programming. Unfortunately this tenet is often ignored or misunderstood.
Dart works hard to preserve this principle in several ways, though it does so imperfectly.
- Dart types are based on interfaces, not on classes. As a rule, any class can be used as an interface to be implemented by other classes, irrespective of whether the two share implementation (there are a few exceptions for core types like numbers, Booleans and strings).
- There are no final methods in Dart. Dart allows overriding of almost all methods (again, a very small number of built-in operators are exceptions).
- Dart abstracts from object representation by ensuring that all access to state is mediated by accessor methods.
- Dart’s constructors allow for caching or for producing instances of subtypes, so using a constructor does not tie one to a specific implementation.
As we discuss each of these constructs we will expand on their implications.
1.2.3 Types in the Service of the Programmer
There is perhaps no topic in the field of programming languages that generates more intense debate and more fruitless controversy than static typechecking. Whether to use types in a programming language is an important design decision, and like most design decisions, involves trade-offs.
On the positive side, static type information provides valuable documentation to humans and computers. This information, used judiciously, makes code more readable, especially at the boundaries of libraries, and makes it easier for automated tools to support the developer.
Types simplify various analysis tasks, and in particular can help compilers improve program performance.
Adherents of static typing also argue that it helps detect programming errors.
Nothing comes for free, and adding mandatory static type checking to a programming language is no exception. There are, invariably, interesting programs that are prohibited by a given type discipline. Furthermore, the programmer’s workflow is often severely constrained by the insistence that all intermediate development states conform to a rigid type system. Ironically, the more expressive the type discipline, the more difficult it is to use and understand. Often, satisfying a type checker is a burden for programmers. Advanced type systems are typically difficult to learn and work with.
Dart provides a productive balance between the advantages and disadvantages of types. Dart is an optionally typed language, defined to mean:
- Types are syntactically optional.
- Types have no effect on runtime semantics.
Making types optional accommodates those programmers who do not wish to deal with a type system at all. A programmer who so chooses can treat Dart as an ordinary dynamically typed language. However, all programmers benefit from the extra documentation provided by any type annotations in the code. The annotations also allow tools to do a better job supporting programmers.
Dart gives warnings, not errors, about possible type inconsistencies and oversights. The extent and nature of these warnings is calibrated to be useful without overwhelming the programmer.
At the same time, a Dart compiler will never reject a program due to missing or inconsistent type information. Consequently, using types never constrains the developer’s workflow. Code that refers to declarations that are absent or incomplete may still be productively run for purposes of testing and experimentation.
The balance between static correctness and flexibility allows the types to serve the programmer without getting in the way.
The details of types in Dart are deferred until Chapter 5, where we explain the language’s type rules and explore the trade-offs alluded to above in detail.