Dill is a statically typed programming language to capture how I want to write and read imperative code. It’s meant to be type- and memory-safe, with an orthogonal set of type-based abstractions, and as low runtime overhead as possible. The most interesting feature is how it makes mutability visible and simplifies management of the value/reference distinction.
- “Imperative locally, immutable-first globally”; local variables are reassignable, but datatypes are immutable by default, and procedure arguments are passed immutably by default and are not reassignable.
- Non-local mutation is made explicit; passing an object mutably is indicated at both the function definition and call site. Otherwise “deep” immutability is enforced.
- No implicit differences in behavior due to using values
vs. references; explicit mutability reduces the number of places where
you have to think about the distinction. You can declare a local
ref
variable, but for function arguments the mutability distinction is enough. Assignment of a mutable object to avar
is always a copy, but you have to write an explicitcopy()
for dynamically-sized types.
- Algebraic datatypes, called
record
andvariant
. Built-inBool
andOption
types have the same syntax as user-defined variant types. Recursive types. - All types are datatypes; there are no classes (containers for a type and methods). As an alternative to method call syntax for call chaining, a pipe operator is in the works, with a parameter marker which also distinguishes mutable and non-mutable arguments.
- Null-safety with explicit nullable (option) types, with syntactic sugar
?
and value promotion convenience features.
- Syntax: keyword-based, no curly braces or significant whitespace, semicolons as statement (not block) terminators. LL(2) parseable (mostly LL(1)), no significant whitespace. Blocks are closed by a slashed keyword (
/if
,/while
) to be more informative than justend
but still short to type. Making code easy to type and even read aloud are also goals.
- Packages are the largest unit of organization, similar to a namespace but aimed at being a library. Multiple source units can be part of the same package. Importing a package with
use
brings in everything in the package, so explicit fine-grained imports aren’t necessary. - Modules are smaller than packages and are meant to group a closely-knit set of types and functions. Importing a module explicitly allows method-call syntax to be used on the procedures in the module, by their first argument.
- Separate compilation by means of auto-generated package specifications. This is strictly a compiler feature—package specs are not part of the language definition proper. What a package exposes is completely specified by its source.
- In progress: generics, with constraints by means of module signatures (the modular equivalent of interfaces). Module imports explicitly specify which signature implementations you’re using.
- Planned: coroutines, probably stackless.
Look in testsrc/ for examples, though many are not up to date with recent syntax changes.
We’ll see how far I get. The backend is LLVM with the Boehm garbage collector, currently targeting only x86-64.