This Week in D April 10, 2016

Welcome to This Week in D! Each week, we'll summarize what's been going on in the D community and write brief advice columns to help you get the most out of the D Programming Language.

The D Programming Language is a general purpose programming language that offers modern convenience, modeling power, and native efficiency with a familiar C-style syntax.

This Week in D has an RSS feed.

This Week in D is edited by Adam D. Ruppe. Contact me with any questions, comments, or contributions.


Major Changes

DMD 2.071.0 was released this week, bringing fixes to long-standing import and privacy bugs, along wit some performance releases and various other bug fixes.

See this blog article on the new import changes to better understand what the import changes mean.

In the library, a clear method was finally added to associative arrays too.

The dmd team has also started using ddoc on dmd itself in an effort to better document the source code so it is more approachable to new users.

In the community

Community announcements

See more at the announce forum.

Tip of the Week

This week, I want to talk about techniques to decouple programs and minimize dependencies in D. Five techniques come to mind:

Java-style interfaces are available if your code fits static substitution well. With interfaces, you need to define a list of methods that you depend on inside the interface. Then, you write functions that take objects that statically fulfill this interface. User code can define other object that fulfil the same interface and are thus usable by your code, without your code necessarily being aware of any particular implementation.

This decouples your code from it dependencies in that it doesn't have to be aware of their existence... but the dependencies are not entirely decoupled from your program: the static nature of interfaces need the dependency must import your module, or both your module and the other module must import the same third module that defines the interface; somehow, both sides of the program need to be aware that they may *potentially* be used together.

Well, that's not exactly true: actually, an interface can be implemented on-the-fly by a third party module via wrappers. D has anonymous classes that you can define inline to simply wrap a list of functions to implement an interface. But, nevertheless, the implementation and interface still need to be tied somehow.

If you want to avoid the static interface and have two modules that have no idea that they have any relation at all to anything in common, a similar, but less structured alternative to interfaces are functions that take delegates.

A delegate is a language-packed data pointer and function. You can create them from objects by taking the address of a method, or you can create them from other functions, or you can define them inline with anonymous functions.

You can write a function to take one or more delegates, then your user functions pass their own implementations to it. Your code doesn't know what user code is passed in, but you can use it all. Combined with default implementations, you can make a hookable function that users can easily modify.

This isn't really much different than interfaces though, except that with interfaces, a group of delegates are tied together into a static entity (and they all must have the same data context pointer). They key difference though is that there's no static name that needs to be shared:

module a;

void a(void delegate() dg);
module b;

void b(void delegate() dg);
module c;

void impl() {
	a( { } );
	b( { } );

Here, module C can be compatible with module a and b, despite there being no shared types at all between modules a and b. This code compiles as is. By contrast, with interfaces:

module a;
void a(SomeInterface obj);
module b;
void b(SomeInterface obj);
module c;

void impl() {
	return new class SomeInterface {
		// implementation
; } )

That code will not compile because SomeInterface is not defined. All three modules will have to import another module that define SomeInterface - and it must be the same module imported from all three. Thus, while the interface is more implicit, it comes with the cost of needing a static definition shared - at least a little coupling tiny between module a and b with some third module, whereas with delegates, they don't even need that.

Even with delegates, there might be a need for a third definition module for shared data structures. You can avoid this by decomposing all data into basic types in the delegates, but this is a bit of a pain too.

But, this third definition module may not be wanted either - you might actually prefer to redefine simple structs. For example, struct Point { int x; int y; } is simple enough to define twice, but if you do, module A's Point will NOT be directly compatible with module B's Point - passing b.Point to a function expecting a.Point will give an error message like arguments (Point) are not compatible with expected type (Point) (yes, the compiler will use the same text for the two different types, it can be very confusing and hopefully will one day be fixed).

You'll need a trick to convert these two types between each other... and I have one! Use obj.tupleof. If the two objects have the same binary layout, then a.tupleof = b.tupleof; will compile! You can also construct it: A(b.tupleof) and convert types that don't know about each other but happen to match.

Interfaces and delegates have parallels in other languages. D has some more unique things too, starting with duck-typed templates. These templates do not check their semantics until someone tries to use them with a specific type: the definition module needs to know absolutely nothing about the user code. The downside, of course, is that user code can cause compile errors inside your code - the compiler doesn't help much with checking correctness ahead of time.

module a;
void a(T)(T t) {
module b;
import a;
void impl() {
	struct T {
		void something() {}
	T obj;

This is even looser than the delegates - you don't need to define all of them in the argument list, you just write the code and let the compiler figure it out later. It isn't a new capability (C++ can do it too, as can dynamic languages), but can be more concise than the delegate list and avoids the third module of the interface definition.

I think this goes too far though: it is too easy to mess it up. A reason why I like to decouple programs is to minimize dependencies, but when two modules are supposed to be able to work together (without NEEDING to be used together). It is possible that another module would use my hooks too, but the main reason I make it hookable is actually for my own use, without forcing extra modules on the user when they don't need it.

As I have written about before, if you use local imports inside the template, the compiler will still let you reference its types.. but the import is processed only if the user tries to use the template so it is instantiated. With this technique, you can get checks on your own modules while being flexible for the end user.

The last trick is if you want two modules to stand alone, but also work together. The exact case in my mind are getting simpledisplay.d and terminal.d to have an integrated event loop... without introducing a dependency between them. The simplest solution is to have a third module holding the event loop implementation that is imported by both. But, a big selling point of both these modules is that they don't need extra imports; they stand alone. So, I can't have a third module.. but I could use an interface or delegates for the event loop hooks in one module, and a lazy import to automatically hook it up together if needed.

It can be extra work in the short term to decouple your software. Sometimes, even more work in the long term, but it has other benefits: your users aren't caught in your dependency web and you aren't caught in a dependency web, so you can change things out without as much worry about breaking unrelated things. Besides, a lot of programmers just prefer using smaller pieces to build their customized whole, and a lot prefer to use a pre-fabricated framework. With a mix of D's techniques like interfaces, delegates, and templates, you can cater to both groups well.

Learn more about D

To learn more about D and what's happening in D: