Delight

Aug 2009, Thomas Leonard

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:

Date read(Clock clock);

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.

Next Step

Try the Delight Syntax page...