Introduction
Delight is an imperative, object-oriented programming language with a
Python-like syntax. Internally, it is based on the D programming
language. Its major features are:
- Python-like syntax
- Classes, interfaces and templates
- Compiles to efficient native code
- Static type-checking
- Dependency injection
- Built-in logging
Here's Hello World in Delight:
import dlt.io: Printer
class Main:
void main(Printer stdout):
stdout.write("Hello World!\n")
Delight is experimental and changing rapidly. It is not suitable for production code.
Delight Syntax
Delight drops the punctuation-heavy style that D inherits from C. Instead, its
syntax is based on Python's.
- A colon (:) introduces a new, indented, block.
- Statements end at the end of a line. Semi-colons are not required.
- Parenthesis are not needed in if, while statements, etc.
For example, this function takes a number and converts it to a string:
string formatNumber(int x):
if x == 1:
return "One"
else if x == 2:
return "Two"
else:
assert x > 2
return "Many"
For more details, see the Delight Syntax page.
Main
An unusual feature of Delight is that, rather than using a static function
or method, execution starts by instantiating the Main class and then running
the main method inside it:
class Main:
void main(Printer stdout):
stdout.write("Hello World!\n")
An obvious question is, why the unusual type signature of main? Why does it return
void (rather than int) and why does it take a Printer (rather than an array
of strings)?
To answer the first question: if main returns normally then the program exits
with an exit status of zero. To exit with a different status, throw
the SystemExit exception, which allows the exit code to be specified, as well
as a message to display to the user. The code can even be zero (success):
here's an alternative version of Hello World:
class Main:
void main():
throw new SystemExit("Hello World!", 0)
As for the Printer argument to main, this isn't fixed (in fact, it's
missing from the previous example). But first, a digression...
Global State
Delight has no global state. There are no module variables, no class static
variables, and no function static variables:
# Error: no module variables!
int x = 0
class Foo:
# Error: no static class variables!
static int i
void foo():
# Error: no static function variables!
static int j
The only way for some code to get access to an existing object is for some other
code to pass it to it. For example:
Foo foo = new Foo()
foo.doSomething()
The doSomething call takes some arbitrary amount of time to execute, and then
either returns, or throws an exception. It does not write to the standard output
(since it wasn't given the stdout object). It does not access the filesystem.
It can't even find out what time of day it is!
Note that this is not about "security" (i.e. protection from malicious
programmers). Rather, it's about encouraging good design by ensuring that
interactions between components have to be explicit.
This explains the inputs to Main.main. The method cannot go and get access
to stdout. It must be given it. The standard library provides a set of named
arguments which can be supplied in this way.
See dependency injection for details.
Note that you can have top-level functions, static class methods and static
inner classes. Only state is disallowed.
Object-Oriented Features
Delight supports classes (with single inheritance) and interfaces:
interface Alerter:
void alert()
class PrintAlerter implements Alerter:
Printer printer
this(Printer p):
printer = p
void alert():
printer.write("Alert!!\n")
this is a constructor. It takes a Printer and stores it in a class
instance variable. Our new class can be used like this:
class Main:
void main(Printer stdout):
Alerter a = new PrintAlerter(stdout)
a.alert()
The constructor code for storing passed-in objects gets repetitive. Delight
offers a short-cut: any class variable marked in is implicitly added to the
constructor arguments and assigned when the object is constructed. The constructor
can be omitted completely if nothing else needs to be done:
class PrintAlerter implements Alerter:
in Printer printer
void alert():
printer.write("Alert!!\n")
NullPointerException
One of my biggest problems with main-stream typed languages is the handling of
null. In D or Java when you say:
the compiler will make sure it passes either a Clock or null.
What's the point carefully adding type annotations everywhere if your program's
just going to get a null and crash anyway? You might as well save yourself some
typing and use Python.
In Delight, object references cannot be null. For example:
import dlt.time: *
Date read(Clock clock):
return clock.now
class Main:
void main():
read(null)
It won't compile:
$ delic -o hi hi.dlt
hi.dlt:8: function hi.read (Clock) does not match parameter types (void*)
hi.dlt:8: Error: cannot implicitly convert expression (null) of type
void* to dlt.time.Clock
If you really want a type that can be null, add ? to make
a maybe Clock type. e.g.
Date read(Clock? clock):
return clock.now
The program still won't compile, though:
hi.dlt:4: Error: attempt to access property 'now' for 'clock' of
type 'Clock?', which may be null.
For more information, see the NullPointerException page.
Logging
All this no-global-state, explicit-object-passing stuff is great, until you
want to debug something. Adding an extra constructor argument for a Printer
so you can output diagnostics is tedious, but you don't need to because Delight
has built-in support for logging:
class Foo:
this():
log_warning("Hello from Foo!")
class Main:
void main():
new Foo()
Prints:
$ ./sayHi
2008-09-19 16:16:13,457 Warn hi.Foo - Hello from Foo!
By default, warning and error level messages are written to stderr, while
info and trace messages are discarded. The Main class can take the
system logManager object as an input and change the threshold if desired, or
reconfigure it to output in other formats and to other places.
See Logging for details.
Credits
The basis for Delight is the Digital Mars D compiler front-end
by Walter Bright (which parses and analyses the source files), the
GNU Compiler Collection (GCC) from the FSF, which generates native code,
and David Friedman's GDC, which connects the two together.
I have changed the lexer and parser to make the syntax Python-like, added
the logging statement and the dependency injection features, and added maybe
types to the type system.