Except when it comes to the allocation and storage of data, variable typing is all about method dispatch — the language must, at some point, map every code-level method call to a compiled entry-point, or report an error.
As discussed in Opening Comments, my intent is for Nexxi to be useful both interpreted and compiled, both for rapid prototyping and for robust code. Interpreted environments tend to prefer weak typing, where type-compatibility checks and method dispatch are done on-the-fly. And rapid prototyping tends to work best when the programmer is not asked to do a lot of bookkeeping. On the other hand, robust code tends to benefit from strong typing, as it enables the compiler to report method-dispatch and type-compatibility errors at compile-time.
My plan for Nexxi is to get the best of both worlds. Nexxi will be strongly-typed, as there is no way around it for robust systems; but it will take advantage of modern conveniences like type-inferencing and signatures to minimize the amount of bookkeeping the programmer must do. It will support generic types, and you will be able to use them for method specialization. It will also introduce a special type — a Dynamic type — that can be used by the programmer to get the compiler to defer type-checking and method-dispatch until run-time. Using these features together, I believe Nexxi can be strongly-typed at almost no cost to the user, and can still be flexible, when that is more important.
Let’s start with a Java example of something I hate:
String string = new String("hello");
It should be obvious to the compiler that new
String("hello") returns a String. In fact, it is
obvious to the compiler, because if you give string an
incompatible type, the compiler will give you error for your trouble.
So the question then becomes: why is the compiler asking for stuff it
already knows?
Now, you could argue that you might want a more general type for
string, and the compiler is giving you the opportunity to
tell it so. This is a valid argument — the language does need to
be flexible enough to allow variable types to be more general than the
objects stored in them. I just don’t see any good reason to require type
markers on every variable declaration, when, most of the time,
such markers are redundant.
Type-inferencing is a process by which the compiler figures out variable types by what gets written into them. Such a resolution process can be costly, as it can require recursive analysis of the call graph, but in the age of fast processors and cheap memory, I believe it is time that we hand this responsibility over to the compiler, and so free up programmer time for more interesting stuff.
In Java and similar languages, types are usually defined in a hierarchy — each type is derived from another, and types on separate branches of the type-hierarchy are incompatible. Signatures are a different way to think about typing: signatures define the list of methods an object must provide in order to be compatible with its user. In some ways, they are similar to Java interfaces, except that signatures are not part of the type-hierarchy — they are applied after the fact. With signatures, you state your requirements, and the compiler makes sure only objects that match are accepted.
Signatures are especially useful in languages that support polymorphic
methods and operator overloading. In essence, using a signature
allows you to define methods that work with any object that supply the
methods you use. For instance, both of Java’s PrintStream
and PrintWriter classes define a println()
method, but the two classes are not related and do not implement a
common interface. In Java, if you wish to support both, you must write
two versions of your algorithm — two versions that are identical
in every respect, except for the declared type of the method parameter.
With signatures, you need define only one version of your algorithm, and
the compiler takes care of mapping the used method name to the actual
entry-point on the object. And because this information is available
at compile-time, the compiler can still report errors immediately,
and produce fast code.
Coupling signatures with type-inferencing means that the programmer need not define most signatures. When type-hierarchy information cannot be inferred, the compiler can generate signatures for each such method parameter, and so give good error messages. And on the call, where the compiler knows the actual type of the object to be passed, the compiler can generate a proxy that directly implements the signature’s interface, and so avoid run-time method dispatch.
In Nexxi, signatures are a first class member of the typing system — just like interfaces. And while you can’t create a signature object directly (just like interfaces), you can use the type to hold any conforming objects you do create.
Generic types extend the compiler type-checking abilities into
extra dimensions. If the compiler is certain that a list contains
only strings, for instance, it can allow the programmer to directly
access String-specific methods for each member of the list.
This improves the readibility and reliability of the code. It further
allows the compiler to make better type-inferencing decisions, which
improves the quality of error messages.
In Nexxi, generic types can nest inside generic types, and the compiler will still apply all typing rules and benefits to each level. This is often referred to as covariant typing. In simpler terms, Nexxi will do the smart thing, not the easy thing, when generics nest.
Signatures and interfaces can be used as generic parameter types.
| in Design: | |
| on site: |
Markdown: The kinds of formatting markup you'd use in an email will probably work here. For more details on what you can do, check out the Markdown docs.