-
Notifications
You must be signed in to change notification settings - Fork 1
Alore design rationale
This pages explains some potentially controversial design decisions in Alore.
Alore has optional typing, i.e. static types do not (have to) affect runtime semantics. Rationale:
- Many programmers are only used to working with dynamically-typed languages. They do not need to learn a new programming approach in order to program in Alore or to use statically-typed libraries. Library designers still get the benefits of static typing.
- Optional typing allows more flexibility for the programmer, as the type system can always be circumvented when it gets in the way.
- Dynamically-typed and statically-typed code have consistent semantics, by definition. Simply adding type annotations does not change program semantics. This makes programs easy to understand and makes the evolution from dynamic to static typing smooth.
- Fully-typed Alore programs that don't use dynamic typing are intended to be type-safe (unlike Dart). (Of course things like casts can still fail and there may be nil value exceptions, as in Java and many other statically-typed languages.)
- Alore type system allows optional typing, but a next step is to also implement a "strict" mode with runtime type-safety for programs that use mixed typing, at the expense of additional implementation complexity and slower compilation speed. The original approach that ignores type declarations can still be used for quicker program startup when desired.
- We have tried to design the Alore type system to support modular optimising ahead-of-time compilation when using "strict" type checking. This could allow using Alore as a general-purpose language in contexts where the overhead of a JIT-based VM is undesirable.
Alore variables are initialized implicitly to nil, unless they have an initialiser expression.
- We need a generic representation for uninitialized members due to optional typing (during object construction), and nil fulfills this role.
- Programmers are used to having null/nil, especially in dynamic and scripting languages.
- Adding non-nil types is under serious consideration, however.
- "Everything is a object" and optional typing precludes different semantics for value types. Besides, having a default "nil" value for integers is arguably better than having a default of "0" as in many languages, since the latter can mask real programming errors.
Alore has local type inference for local variables (only the initialiser expression is used for type inference, not wider context).
- Full type inference is undecidable for an object-oriented language like Alore with a nominal type system, at least without some major limitations and complexity.
- Local type inference allows generating precise and useful type check error messages for almost all type errors.
- Local type inference is easier to implement and handles many common cases very well. In cases where it cannot infer the type (e.g. function argument types), the types serve as useful documentation and allow the type checker to produce precise and useful error messages.
- Local type inference simplifies the bulk of writing code (i.e. bodies of functions) much and makes
the code look cleaner. Especially spelling out generic types such as
Map<Int, (Int, Str)>
everywhere would be annoying. - Local type inference allows dynamically-typed and statically-typed code look similar, making Alore feel like a single unified language instead of two languages crudely put together.
- Types of member variables and global variables are not inferred for several reasons (only types of
local variables are inferred):
- Giving them explicitly gives useful documentation. Globals and members are visible to a larger part of code than locals, so it makes sense to spend the effort to document them.
- Members and globals often have an initializer that is not specific enough for type
inference (e.g.
nil
or[]
), so often the type would have to be given anyway. For consistency it's good to have to specify it always. - Local variables are more numerous than members or globals. Giving the type of members or globals is only little effort, but giving the type of each local would be inconvenient and clutter the code.
- Dynamically-typed globals simply lack a type declaration; there is no special syntax. With type inference we would need different syntax for dynamically-typed variables and statically-typed variables whose type is inferred. This would complicate the syntax with little benefit.
Alore overloads the "as" keyword for three purposes:
- Casts (
expr as TypeName
) - Variable and function return type declarations (
x as Type
) - Type application (specifying generic type parameters for generic types or functions)
(
expr as <T1, ...>
)
They all share the same pattern:
- The right operand specifies the type of the left operand (or some aspects of it)
By virtue of making type application resemble variable type declaration, we can use a short-hand notation
for constructing generic instances that is visually similar to ordinary type declaration, resulting
in a cleaner look. Alore has the goal of making statically-typed and dynamically-type code have
a similar feel, and Java-style generic syntax would be visually too obtrusive.
The example below illustrates the issue. The declarations of a1 and a2 below are equivalent. The first form
is usually preferred, since it is more compact and giving the type Array
explicitly does not
make the code any clearer.
def f(x as Int)
-- Create an empty array of integers; also declare type of variable to Array<Int>.
var a1 = [] as <Int> -- Use type application to declare type of []; use type inference for a1
var a2 = [] as Array<Int> -- Declare type of a2; infer type argument of []
end