Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Functions

Function declarations are defined by the following syntax.

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

SimpleFunctionDeclaration:
	'function' Identifier FunctionBody

FunctionBody:
	Parameters (Statement | '=' Expression)

Parameters:
	'(' [('this' ':' Type | Parameter) {',' Parameter} [',' 'vararg']] ')'
	'(' 'vararg' ')'

Parameter:
	Identifier [':' Type] ['=' Expression]
	
Type:
	BasicType
	'!' 'null'
	'any'

BasicType:
	BasicType '|' BasicType
	'null'
	'bool'
	'int'
	'float'
	'char'
	'string'
	'table'
	'array'
	'function'
	'class'
	'instance' [(Identifier {'.' Identifier}) | ('(' Expression')')]
	Identifier {'.' Identifier}
	'namespace'
	'thread'
	'nativeobj'
	'weakref'

Functions can be declared anywhere.

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

If you declare a function without the 'local' or 'global' keywords, it defaults to a global at module-level, and to a local everywhere else. Thus the following code:

module x
function foo()
{
	function bar() {}
}

is exactly equivalent to:

module x
global function foo()
{
	local function bar() {}
}

which is in turn equivalent to:

module x
global foo = function foo()
{
	local bar
	bar = function bar() {}
}

The same rules apply to function declarations as apply to variable declarations as far as scoping and shadowing.

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 local 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

local function foo(x)
{
	if(x > 5)
		bar(x) // 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 and because there are no types, it is not possible to implement function overloading by declaring the function multiple times. 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.

The Function Body

In most C-style languages, the body of a function must be a block statement. MiniD relaxes this requirement allowing any statement to be used as the function body.

function foo(x)
	return x + 2

In fact this function can be written even more tersely. You can make a function that just returns a value by using an equals sign followed by an expression as the body:

function foo(x) = x + 2

This extends to function literals:

arr.sort(function(a, b) = a.x <=> b.x)

And in fact there is an even shorter form for function literals, borrowed from Haskell:

arr.sort(\a, b -> a.x <=> b.x)

This last bit of code is exactly equivalent to:

arr.sort(function(a, b) { return a.x <=> b.x })

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 a conditional 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)
{
	// the default param above is the same as if we wrote "y ?= 10" right here.
	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

Parameter Type Constraints

MiniD is a dynamically-typed language. This means that static checks, including compile-time type checking, are impossible. The dynamic nature of the language is both a strength and a weakness: it can lead to much more general, simple-to-write code, but at the same time it can make errors that would be a trivial compile-time error in a statically-typed language impossible to see or diagnose. If a function expects a certain type or types for its parameters, and another type is passed, the function may still work right, or it may malfunction in confusing ways, or it may seem to work correctly, but return bogus values or store bogus data that cause confusing errors later in program execution.

In order to help alleviate some of these typing issues, MiniD supports parameter type constraints. With these, you can restrict what types parameters may accept. The types are still checked at runtime, but you will at least get a reasonable error message (and a graceful failure instead of a malfunctioning program).

Don't let the syntax or the idea of parameter type constraints fool you -- parameter type constraints are not the same as statically-typed parameters. You can't use them to overload functions with different "signatures;" the typechecking occurs inside the function and is not part of its public interface. They also cannot be checked at compile time. The function must actually be called in order for the typechecking to occur.

Here are the grammar rules pertaining to parameter type constraints:

Type:
	BasicType
	'!' 'null'
	'any'

BasicType:
	BasicType '|' BasicType
	'null'
	'bool'
	'int'
	'float'
	'char'
	'string'
	'table'
	'array'
	'function'
	'class'
	'instance' [(Identifier {'.' Identifier}) | ('(' Expression')')]
	Identifier {'.' Identifier}
	'namespace'
	'thread'
	'nativeobj'
	'weakref'

Parameter type constraints are optional and come after the parameter name (and before any default value). There are two "special" types which a parameter can take: "any" and "!null".

function f(x: !null, y: any) {}

"any" means a parameter can take any type. This is in fact the default behavior: if a parameter is declared without a type constraint, it's the same as declaring it as "any". "!null" means that the parameter can take any type other than null. You could use this constraint to mean that the parameter is required: if the function is called with an insufficient number of parameters, an error would be given since unpassed parameters are given null. So the above function can be called with one or more parameters.

The other types are (mostly) self-explanatory. You can have a parameter take multiple possible types by joining them with the 'or' operator. For example:

function square(x: int|float) { ... }

This function takes a parameter which must be either int or float.

If you want a parameter to take instance types, you have a few options. Declaring a parameter as just taking "instance" means that it will accept any instance type. If there is an class type "X", you can simply use that as the parameter type:

function f(x: X, y: instance)
{
	// x will take instances instantiated from X or any class derived from X
	// y will take any instance type
}

Declaring a parameter like "x" has been above is identical to writing "x: instance X". This second, longer form is useful in case you have a class that has the same name as one of the basic types (such as "int" or "float", which aren't keywords). So while "x: int" means the parameter will take an integer, "x: instance int" means it will take an instance of the class whose name is "int".

There is one last form for instance types: by default, instance types are restricted to period-separated components, such as "X" or "mymodule.Foo". However if you want to accept an instance whose type is given by an expression, such as "types[foo]", you should use the form "x : instance(types[foo])". This probably won't come up all that often.

The this Parameter

Every function in MiniD has an implicit first parameter named this. this contains the context in which the function is to execute, that is, the object out of which it was indexed.

If a function is called as a free function (i.e. not as a method), the this parameter will contain null. The following code:

function func()
	writeln(this)

func()

will print out "null".

When you index a function out of an object using a dot expression, the call becomes a method call. In a method call, the identifier or 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, ant is used as the this parameter to the function when it's called. So the following code:

local t =
{
	function f()
		writeln(this)
}

t.f()

will print something like "table 0x0012abc0". 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 this parameter to a function, you can do so by using the with keyword:

function func()
	writeln(this)

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

You can also use with with method calls, but not with supercalls (see the section on Classes for information on them).

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, as well as through some special syntactic forms (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"

The problem with 'vararg' is that it can be inefficient to access variadic arguments within a function, since you have to create an array to do anything useful. To help with this, 'vararg' can be accessed almost like a pseudo-array.

function foo(vararg)
{
	// get the number of varargs with #vararg
	writefln("got {} args", #vararg)

	for(i: 0 .. #vararg)
	{
		if(!isString(vararg[i])) // get or set the i'th vararg by indexing it
			vararg[i] = toString(vararg[i])
	}

	// slice varargs.  this gives multiple values, just like 'vararg' itself
	writefln(vararg[1..])

These operations on 'vararg' are more efficient than creating an array and manipulating that.

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 the outer variables 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 "fibers," "green threads," "stack threads" or "microthreads." A coroutine is a separate thread of execution, but unlike preemptive 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 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 shorter to type.

That last example showed the use of a coroutine as a generator, which is a thing which, with successive calls, returns a sequence of values. You can actually iterate over a coroutine directly with a foreach loop. In order to iterate over a thread object, it must satisfy the following criteria: it must be in the 'initial' state when the foreach loop starts. When it enters the 'dead' state, iteration will end. It therefore must use 'yield' to yield all its values, as returning some values will cause it to enter the dead state, causing the loop to terminate.

Furthermore, the rule about a 'null' index terminating the loop does not apply to iteration over coroutines, since instead the coroutine's state is used to determine when iteration should end.

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

function countDown(x) =
	coroutine function()
	{
		while(x > 0)
		{
			yield(null, x) // notice, null index!
			x--
		}
	}

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

writefln()

function forEach(t) =
	coroutine function()
	{
		foreach(k, v; t)
			yield(k, v)
	}

foreach(k, v; forEach({hi = 1, bye = 2}))
{
	writefln("key: ", k, ", value: ", v)
}

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