Overview of static and dynamic typing
Alore is a programming language that supports both dynamic and static typing, and a mixture of both in the same program. This section gives an overview of some central issues related to static typing. We also discuss when to use static types, how static typing differs from dynamic typing, and why it can be very useful to have both static and dynamic typing available at the same time.
A dynamically-typed variable can hold values of any type (e.g. integers, strings or arrays). Operations on dynamically-typed variables are checked at runtime. In a dynamically-typed program all (or most) variables are dynamically typed.
Statically-typed variables are declared to hold values of a specific type. When using static typing, a program is type checked before running it. Type checking verifies that operations on a value are valid for the type of the value. For example, trying to add a string to an integer results in a type check error in a statically-typed program. In a dynamically-typed program the error would be caught only at runtime, and only when that particular operation would be performed.
This document assumes that the reader has a working knowledge of the dynamically-typed subset of Alore and the standard library, in particular the std module. It is sufficient to be familiar with the content of Introduction to Alore. This document focuses on the type system and does not repeat information in the linked general introduction.
It is helpful if the reader has a basic understanding of programming in a statically-typed language such as Java, C++ or C#.
A static typing example
In this section we present a small example program using both dynamic and static typing, and discuss their differences.
The following dynamically-typed program approximates two triangles using ASCII characters:
def Triangle(n, ch) for y in 0 to n WriteLn(ch * (y + 1)) end end Triangle(2, '*') Triangle(4, '\')
Now let's save the program as program.alo and run the program. The output looks like this:
$ alore program.alo * ** \ \\ \\\ \\\\
Declaring types
We can modify the program to be statically-typed by declaring the types of some variables. The as keyword is used to introduce these type annotations (we highlight all changes):
def Triangle(n as Int, ch as Str) for y in 0 to n WriteLn(ch * (y + 1)) end end Triangle(2, '*') Triangle(4, '\')
The annotations declare that Triangle must be called with a first argument that is an integer and a second argument that is a string. The annotations also declare the types of the arguments within the function body. The type of the local variable y is inferred to be Int. The output of the function remains identical to the dynamically-typed version.
We can also specify the type of y explicitly, by replacing the second line of the example with the line below:
for y as Int in 0 to n
Running the type checker
If you save the modified program as program2.alo, you can now type check it by running alore with the -c option:
$ alore -c program2.alo $
There were no type errors since there was no output from the type checker. You can type check the program and also run it (if there are no errors) with the -t option:
$ alore -t program2.alo * ** \ \\ ...
Introducing type errors
Now let's add a new line to the end of both of the program variants:
... Triangle(2, '*') Triangle(4, '\') Triangle(3, 3)
The dynamically-typed program still produces some output, but perhaps not what we'd expect (and without reporting a problem):
$ alore program.alo
*
**
\
\\
\\\
\\\\
1
3
9
However, static type checking produces the following error when type checking the statically-typed program variant:
$ alore -c program2.alo program2.alo, line 8: Argument 2 to "Triangle" has incompatible type "Int"
Let's do another modification:
... Triangle(2, '*') Triangle(4, '\') Triangle('#', 3)
Note that the arguments in the third call are in reverse order. Now the dynamically-typed program terminates with a runtime type error after producing some output:
$ alore program3.alo
*
**
\
\\
\\\
\\\\
Traceback (most recent call last):
at main level (program3.alo, line 8)
Triangle (program3.alo, line 2)
TypeError: Non-integer Range upper bound (Str)
Static type checking again detects the above error without having to run the program:
$ alore -c program4.alo program4.alo, line 8: Argument 1 to "Triangle" has incompatible type "Str" program4.alo, line 8: Argument 2 to "Triangle" has incompatible type "Int"
The above examples highlight some of the differences between dynamic and static typing, although many details were omitted. The next section discusses the differences more broadly.
Note: Since Alore has an optional type system, you can always run programs that have type check errors. Simply omit the -c or -t option, and the interpreter will skip all type declarations. This can be very useful in the middle of a large program modification. It is nice to be able to debug a program with potentially hundreds of type errors in it, instead of being forced to wait until no type errors remain.
Why both static and dynamic typing?
At the present, almost all programming programming languages support only static or dynamic typing, or they clearly favor one of them. Alore is different in having excellent support for both of them. The philosophy of Alore is based on the observation that both dynamic and static typing are useful, but in different circumstances. These circumstances are always changing and difficult to predict, and it is undesirable to have to restrict oneself to one particular way. Alore gives the flexibility of choosing when static or dynamic typing is the right alternative, and the ability to switch between these when circumstances change. Alore does this without the hassle of having to support two different programming languages, with their different syntaxes, idioms, libraries and tools.
Dynamic and static typing have different costs and benefits. Picking the best one for a particular task is not always easy.
Static typing has the following main benefits:
- It allows statically (without running the program) detecting many programming errors quickly, reliably and automatically. This helps reduce the number of bugs and reduces the time spent on debugging.
- Type declarations serve as automatically-checked documentation. They make programs easier to understand and maintain.
- Static typing may improve runtime efficiency. (Note that the Alore runtime cannot currently take advantage of type declarations, but this will likely change in the future.)
Dynamic typing has a different, complementary set of benefits:
- Dynamic typing is conceptually simpler and easier to understand than static typing, especially when using powerful container types such as Alore arrays, tuples and maps. This effect is pronounced for non-expert programmers.
- Dynamic typing is more flexible. A static type system always restricts what can be conveniently expressed. Programming with a static type system often requires more design and implementation effort.
- Dynamic typing results in more compact programs, since it is more flexible and does not require types to be spelled out.
The benefits of static typing are more pronounced for large and complex programs. It offers little benefit over dynamic typing when writing short scripts and prototypes, for example. In these cases it mainly slows down the programmer, and dynamic typing is preferable.
Features of Alore type system
The list below presents some of the main features of the Alore type system. The rest of this document introduces these features in more detail:
- Alore allows arbitrary mixing of dynamic and static typing within a
program:
- Dynamically-typed code may use statically-typed libraries and vice versa, without restriction.
- Dynamically-typed classes may inherit statically-typed classes and vice versa.
- Dynamic and static types can be mixed within a single function, a class or an expression.
- Alore supports generic types and generic functions. Generic types with parameters are most commonly used for collection types, e.g. Array<Int> for an array of integers. User-defined types can be generic.
- The type system supports type inference for local variables and generic type parameters. This greatly simplifies working with generic types.
- Alore has tuple types and functions types. For example, the 2-item tuple (2, 'x') has type (Int, Str).
- Interface types allow a simple form of multiple inheritance. Retroactive definition of interfaces for existing types is possible, and even for built-in types such as Int or Str, using bind declarations.
- Alore has an optional type system: declared types cannot arbitrarily change the behavior of programs. This allows selectively ignoring types when running programs, which makes it possible to run and debug programs with static type errors in them. It also enables smooth evolution from dynamically-typed programs to static typing.
- Alore models function overloading by using intersection types. Unlike ordinary static function overloading, this is compatible with an optional type system.
More examples
The Alore distribution includes several example programs that show how to use static typing. Here are a few:
- demo/cgi/wiki.alo (a simple wiki application)
- demo/classes/complex/complex.alo (a complex number class)
- demo/scripts/wordfreq.alo (a text file analysis script)
If you feel more adventurous, you can also have a look at Alore standard library module sources (under lib/) and the Alore type checker (under check/).