Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.
Version 38 (modified by JarrettBillingsley, 16 years ago)
--

Functions

Function declarations are defined by the following syntax.

FunctionDeclaration:
	['local' | 'global'] SimpleFunctionDeclaration

SimpleFunctionDeclaration:
	'function' Identifier Parameters BlockStatement
	
Parameters:
	'(' [Identifier ['=' Expression] {',' Identifier ['=' Expression]} [',' 'vararg']] ')'
	'(' 'vararg' ')'

Functions can be declared anywhere.

There are two places the function can end up - in a local or in a global.

If you use a local function declaration, or a function declaration with no preceding keyword, a new local variable will be defined, and the function will be inserted into it. In this way, the code:

local function foo() {}
function bar() {}

Is exactly equivalent to:

local foo; foo = function foo() {};
local bar; bar = function bar() {};

Since it's declaring a local, it is an error to declare a local function that collides with or shadows another local variable.

The other kind of location is a global function. Again, the code:

global function foo() {}

Is exactly equivalent to:

global foo = function() {};

The same rules apply to global function declarations as apply to global variable declarations.

A problem that comes up is when local functions need to be able to "forward reference" other local functions. Since function declarations are compiled in order, local functions declared earlier in the code do not know of later functions. So if you have mutually recursive functions, you must "forward declare" the second one with a local declaration, and then define the function later:

local bar;

function foo(x)
{
	if(x > 5)
		bar(x); // now calls local bar
}

// Note the use of the "named function literal" to improve debugging messages.
bar = function bar(x)
{
	--x;
	foo(x);
};

Because a function declaration is just an assignment, it is not possible to implement function overloading in the way accomplished by many C-style languages:

function foo(x)
{

}

function foo(y)
{
	// this is an error; redeclaration of the local "foo"
}

Instead, you should create one version of the function, and in it, have multiple control paths which determine what to do based on the types of the parameters.

Parameters

Parameters are, simply put, local variables in the function which are given values by an invocation of the function.

The parameters to a function are something of a suggestion in MiniD. Callers are not required to call the function with the correct number or type of arguments. For example:

function foo(x, y)
{
	writefln("foo: ", x, ", ", y);
}

foo(); // prints "foo: null, null"
foo(4); // prints "foo: 4, null"
foo(8, "hi"); // prints "foo: 8, hi"
foo(1, 2, 3); // prints "foo: 1, 2"

As you can see, any parameters which don't have values passed to them are given the value null, and any extra arguments to the function are simply discarded.

You can also use default parameters in MiniD. They are very forgiving: you can put any expression evaluatable at run-time as the default value, you can reference other parameters before or after, and you can put non-default parameters after default parameters. This is because default parameters are just sugar for writing an optional assignment to the parameter at the beginning of the function. Default parameters are then evaluated in the order they are declared. Here is an example of default parameters:

function foo(x, y = 10, z)
{
	writefln(x, ", ", y, ", ", z);
}

foo(3, 4, 5); // prints 3, 4, 5
foo(2); // prints 2, 10, null
foo(5, null, -1); // you can "skip" parameters by giving them null

There is actually at least one parameter passed to every function in MiniD, and that is the this parameter.

The this Parameter and Function Context

Every function in MiniD has an implicit first parameter named this. this contains the context in which the function is to execute.

For normal functions, the context has little meaning. Usually it is just the module namespace of where the function was defined. So, the following code:

module foo;

function func()
{
	writefln(this);
}

func();

will print out something like "namespace .foo". This is because the this parameter is automatically determined at the call site to be the namespace where the function was defined.

When you index a function out of an object using a dot expression, the call becomes a method call. In a method call, the expression to the right of the dot is the name of the method to look up. The expression to the left of the dot is where the method will be looked up, and also is the context of the function when it's called. So the following code:

local t =
{
	function f()
	{
		writefln(this);
	}
};

t.f();

will print something like "table 0x0012abc0" (the address will be different). This is because when we access f from t, t is passed as the this to f.

If you want to use an arbitrary value as the context for a function, you can do so by using the with keyword:

function func()
{
	writefln(this);
}

func(with 5); // prints 5, since 5 is passed as the 'this' parameter

The this parameter also plays a part in global lookup. When you try to access a variable that is a global (i.e. is not a local in this function or in any enclosing functions), the variable is first looked up in the this parameter. If it doesn't exist, lookup then continues on to the module namespace (and then continues up the hierarchy until it finds a global or not). This behavior allows methods to reference members of the class or table in which they were defined without using the this explicitly.

class C
{
	mName = "";

	function constructor(name)
	{
		// Implicitly accessing mName.
		// Same result as 'this.mName = name'.
		mName = name;
	}

	function toString()
	{
		// Again implicitly accessing mName.
		return format("C: name = ", mName);
	}
}

Variadic Functions

A variadic function is one whose parameter list ends in the "vararg" keyword. Any extra arguments, instead of being discarded, will be accessible within the function by means of a vararg expression (see Expressions for more info on that). Basically, a vararg expression will return all values passed, in the order that they were passed.

function foo(x, y, vararg)
{
	writef("foo: ", x, ", ", y);

	// This constructs an array whose members are simply the arguments passed
	local args = [vararg];

	foreach(i, v; args)
		writef(", args[", i, "] = ", v, " ");
}

foo(3, 4); // prints "foo: 3, 4"
foo(5, 6, 7); // prints "foo: 5, 6, args[0] = 7

Because a vararg expression returns a list of values, it can be used to "forward" varargs to other variadic functions:

function myWritefln(vararg)
{
	writefln("myWritefln: ", vararg);
}

myWritefln(4); // prints "myWritefln: 4"

Closures

In D, there is a concept called dynamic closures. Dynamic closures are nested functions which are able to access variables in the scope of their enclosing function. The problem is that once the outer function returns, all those variables disappear, and so the closure is no longer valid as it references variables which no longer exist.

In MiniD, there are no dynamic closures, only static closures. In fact every function you declare is a static closure. Because function declarations are really assignments of a function literal to a variable, it can be said that a function literal creates an instance (a "closure") of the function body which it defines. So every function is a closure.

A static closure is a kind of closure which solves the problem of dynamic closures. That is, a static closure can access outer variables, but once its enclosing function returns, those variables can still be accessed. MiniD accomplishes this through the use of "upvalues," much like Lua.

Any function defined inside another function will have access to its enclosing function's local variables (those which have already been defined, that is). When the enclosing function is still "alive", modifying those outer variables (from now on, called "upvalues") will modify those of the enclosing function:

function outer()
{
	local x = 1;

	function inner()
	{
		++x;
		writefln("inner x: ", x);
	}

	writefln("outer x: ", x);
	inner();
	writefln("outer x: ", x);
}

Calling outer() would yield an output of:

outer x: 1
inner x: 2
outer x: 2

This is because the function inner() can access outer()'s local variables (in this case, x).

Once an inner function is returned, however, something very interesting happens:

function outer()
{
	local counter = 0;

	function count()
	{
		++counter;
		writefln("counter: ", counter);
	}

	count();

	return count;
}

local func = outer(); // prints "counter: 1"
func(); // prints "counter: 2"

Notice that outer() can call count(), but once count() is returned (that instance of it, anyway), it can still be called, and can still access the upvalue counter. This is a static closure.

Coroutines

In addition to normal function execution, MiniD also supports a feature called coroutines, also known as "collaborative multithreading," "lightweight threads," or "microthreads." A coroutine is a separate thread of execution, but unlike typical approaches to multithreading, execution of threads only switches when a coroutine explicitly yields to another thread. In addition, there is also the concept of a "main thread" or "coordinator thread" which manages all the other threads.

MiniD supports coroutines at a syntactic level, unlike Lua and Squirrel. Coroutines are based around a type called thread, and there are two syntactic constructions which are used with coroutines: the coroutine expression and the yield expression.

You create a coroutine using the coroutine expression. This expression takes a single parameter and constructs a thread object from it. The parameter must be a MiniD closure, either written in script or native code. Some examples of use:

function foo(x)
{
	writefln(x);
}

local co1 = coroutine foo;

local co2 = coroutine function(x)
{
	writefln("hey: ", x);
};

writefln(typeof(co1)); // prints "thread"

These are pretty useless coroutines, but this is just to demonstrate the syntax.

Once you have created a coroutine, it is in the initial state. This means it hasn't started yet; it's just waiting to be used. You perform all resuming of a coroutine by simply calling the coroutine's thread object:

co1(5); // prints 5
co2("bye"); // prints "hey: bye"

Since these are very simple coroutines, they finished running their body after just one call. Since their function has returned, they are now in the dead state, as the .state() property of the thread shows:

writefln(co1.state(), ", " co2.state()); // prints "dead, dead"

Okay, so these coroutines aren't very useful. What makes them useful is the yield expression. The yield expression allows you to pause execution of a coroutine and return control back to the thread that resumed it. A yield can happen at any call depth. A yield expression looks and works a lot like a function call. When you give parameters to the yield expression, they are returned to the calling thread as results from the call that resumed the coroutine. When this coroutine is resumed next time, any parameters that are passed to it are returned from the yield expression. Here is an example showing communication between the main thread and a coroutine:

local co = coroutine function co(x, y)
{
	writefln("Co has begun with parameters: ", x, ", ", y);
	local r1, r2 = yield("I've begun");
	writefln("Back in co, main gave me: ", r1, ", ", r2);
	yield("Thanks for the values");
	writefln("Co is about to return, bye");
	return "I'm finished";
};

writefln("In main, the coroutine says: \"{}\"", co(1, 2));
writefln("In main, the coroutine says: \"{}\"", co([1, 2, 3], "hi"));
writefln("In main, the coroutine says: \"{}\"", co());

This outputs:

Co has begun with parameters: 1, 2
In main, the coroutine says: "I've begun"
Back in co, main gave me: [1, 2, 3], hi
In main, the coroutine says: "Thanks for the values"
Co is about to return, bye
In main, the coroutine says: "I'm finished"

The co(1, 2) call starts the coroutine and passes 1 and 2 as the parameters to the coroutine. Note that when this happens, the 'this' is also passed to the coroutine. The context pointer is only passed on the first call to the coroutine (i.e. when it transitions from the initial state to another state). Co reports its parameters, and then yields the message "I've begun" to the main thread, which prints that out. Then the main thread calls co again with [1, 2, 3] and "hi", which are returned from the yield expression in co into r1 and r2. Co prints those out, and yields again with the message "Thanks for the values". Main prints that out, and then resumes co one last time with no parameters. Co says it's about to return. Notice that it returns its "I'm finished" message rather than yielding it. Returning values from coroutines will end the coroutine, but those values will still be passed to the calling thread, just like a yield expression does. The main thread then prints out the final message. At this point, the return value of "co.state()" would be "dead".

Exceptions work with coroutines as well. If an exception is thrown inside a coroutine, it will propagate up the coroutine's call stack as usual. If the exception leaves the coroutine (i.e. is thrown out of the top function), the coroutine is made "dead" and the exception will propagate into the thread that resumed the coroutine from the point where the coroutine was resumed.

local co = coroutine function co(x)
{
	// Just pause as soon as we come in
	yield();

	try
	{
		while(x > 0)
		{
			yield(x);
			x--;
		}
		
		throw "Done";
	}
	catch(e)
	{
		writefln("An exception is leaving the coroutine!");
		throw e;
	}
};

try
{
	// Just starting up the coroutine.
	co(4);
	
	while(!co.isDead())
		writefln(co());
}
catch(e)
{
	writefln("In main, caught: ", e);
}

Output:

4
3
2
1
An exception is leaving the coroutine!
In main, caught: Done

Notice also in the loop in the main thread, it uses "co.isDead()" rather than "co.state() == "dead"". It's just a little performance enhancement.

That last example showed the use of a coroutine as a generator, which is a function which, with successive calls, returns a sequence of values. Since this is very similar to an iterator, you can actually iterate over a coroutine with a foreach loop. When you do this, the coroutine must follow a set of rules: The coroutine must take no more than one parameter. The coroutine must yield once before it starts returning values. The coroutine must yield one or more values. When the coroutine has no more values to yield, it must return and thus enter the "dead" state.

Here are some examples of generator coroutines being used with the foreach statement:

local countDown = coroutine function countDown(x)
{
	yield(); // yield once
	
	while(x > 0)
	{
		yield(x);
		x--;
	}
};

foreach(v; countDown, 5)
	writefln(v);
	
writefln();

local forEach = coroutine function forEach(t)
{
	yield(); // yield once

	foreach(k, v; t)
		yield(k, v);
};

// Notice that we have a dummy variable, _, as the first index in this loop.
// This is because the thread opApply function always includes the iteration index
// number as the first index.  The results from the coroutine start at the second index.
foreach(_, k, v; forEach, {hi = 1, bye = 2})
	writefln("key: ", k, ", value: ", v);

When we use the foreach loop on a coroutine, the second value in the container part of the loop is passed as the parameter to the coroutine. (Additionally, the context passed to the coroutine is the coroutine itself.) Then the coroutine is resumed and its results assigned into the indices until either the first index is null or the coroutine is dead.

There are actually five possible states a coroutine can be in. I've mentioned two -- initial and dead -- and kind of shown you a third, suspended. When a coroutine yields, it enters the suspended state. You can only resume coroutines which are initial or suspended. The other two states are waiting and running. A coroutine is waiting if it itself resumes another coroutine. A coroutine is running if it is the currently-running coroutine; that is, if a coroutine calls ".state()" on itself, its state will be "running". This contrived example shows all those possible states:

local co, co2;

co = coroutine function()
{
	writefln(co.state());
	co2();
	yield();
};

co2 = coroutine function()
{
	writefln(co.state());
};

writefln(co.state());
co();
writefln(co.state());
co();
writefln(co.state());

This outputs:

initial
running
waiting
suspended
dead