A quick look at D

@Date: 15 Jan 2017, @Author: Dr. Chibisi Chima-Okereke

Introduction

I came across the D programming language some time ago while researching C++ templates and found D much clearer in syntax and approach. D has very powerful template programming features for generic and metaprogramming, this is what excited me and has drawn me into the language.

D is a static programming language that some regard as an upgrade to C++, D is also influenced by Java and various other programming languages. It aims to have the performance and safety of compiled languages while allowing the user the expressiveness of a dynamic programming language like Python. D embraces different programming paradigms including imperative, object oriented, and functional programming. There is also support for concurrency and parallel programming. More information about the D programming language can be found at the D official website and wikipedia

In this article, we present basic approaches one can take to writing code for numeric computing in D. Our examples focus on two simplified BLAS (Basic Linear Algebra Subroutine) functions. The scal function is for scaling the elements of an array or vector by some numerical factor and the dot function calculates the dot product of two vectors (or arrays), see IBM’s ESSL documentation for more details. Our versions do not worry about incrementation different from 1. The code for this article is located in our GitHub repo

Writing a D function

Below is one approach you can take for a simplified scal function using a foreach loop:

/* File name: scal.d */
import std.stdio : writeln;

double[] scal_double(double[] x, in double a)
{
    foreach(i, el; x)
    {
        x[i] *= a;
    }
    return x;
}

void main(){
    double[] x = [1, 2, 3, 4, 5, 6];
    writeln(scal_double(x, 3));
}

Compiling the output

There are three D compilers, the reference compiler DMD, the GCC-based compiler GDC, and the LLVM compiler LDC. To compile with LDC on Linux:

ldc2 scal.d -boundscheck=off && ./scal

As a general tip, if we are confident that our index remains in the bounds of our array, we can turn off bounds check which gives us a performance boost. One thing you will notice if you go on to write more D code is that the compiler is FAST - much faster than C/C++. You will also notice that if you make a mistake, the error output you get is clear and informative. Features like this make working in the D language feel seamless from code to output

If you have never seen D code before, the import command is used to obtain the writeln function from std.stdio D’s i/o module and double[] means an array of doubles. The scal_double function takes in a double array x, and a double a and multiplies each element of x by a and returns the result as a double array. The curious in keyword next to the double a declaration makes it clear that a is an input to the function, and an error will occur if you attempt to modify it. Effectively in declares the variable a as const exactly how it would be specified in C/C++. So I could have used const or more strictly immutable for the double a parameter with a similar effect, however marking an input parameter as immutable requires that the variable input also be immutable.

D also supports C/C++ style for loops:

for(int i = 0; i < x.length; ++i)
{
    x[i] *= a;
}

Template functions

But what if I want this function to work for types other than doubles? I could violate the don’t repeat yourself (DRY) principle and write more functions substituting double for my required type or we could use function templates to make the function generic - the same principle as C++ templates but so much easier to write in D.

Below is the scal function written using D’s function template shorthand:

X[] scal(X)(X[] x, in X a)
{
    foreach(i, el; x)
    {
        x[i] *= a;
    }
    return x;
}

This same type of shorthand is also valid for structs, classes and interfaces - come on, is this not much nicer and clearer than C++ angle bracket template notation? If you have not come across templates previously, think of X as a placeholder for any specified type. The long-form template notation is given below:

template scal(X)
{
    X[] scal(X[] x, in X a)
    {
        foreach(i, el; x)
        {
            x[i] *= a;
        }
        return x;
    }
}

This long-form reveals that the template is eponymous.

Now that we have made the function generic, we can call the function using:

import std.stdio : writeln;

X[] scal(X)(X[] x, in X a)
{
    foreach(i, el; x)
    {
        x[i] *= a;
    }
    return x;
}

void main(){
    float[] x = [1, 2, 3, 4, 5, 6];
    /* explicit notation */
    writeln(scal!(float)(x, 3));
    /* Short cut notation */
    writeln(scal(x, 3));
}

D’s full template instantiation notation uses an exclamation mark. At the point you write scal!(float) a float version of the scal function is created, the full declaration is then scal!(float)(x, 3), note that for single parameter templates, the form scal!float(x, 3) is also acceptable. The D compiler is clever enough to infer type from scal(x, 3).

The simplified dot BLAS function calculates the dot product of two arrays. Here is the template version using a loop:

X dot(X)(in X[] x, in X[] y)
{
    X output = X(0);
    foreach(i, el; x)
    {
        output += el*y[i];
    }
    return output;
}

Array notation

If all I am doing is multiplying an array by a number, do I really have to use a loop? It turns out that D has some builtin array sugar for such situations:

X[] scal(X)(X[] x, in X a)
{
    x[] *= a;
    return x;
}

Array notation for the dot case is given below:

import std.algorithm.iteration: sum;
X dot(X)(X[] x, X[] y)
{
    x[] *= y[];
    return sum(x);
}

Uniform Function Call Syntax

D also has uniform function call syntax (UFCS) that allows a function to be called using the receiver as the first parameter:

auto x = [1, 2, 3, 4, 5, 6];
writeln(x.scal(3));

It allows us to have chaining notation which dovetails nicely with function programming styles. The auto keyword is for automatic type deduction, it means we do not have to explicitly specify the type of x, note however that the output of x.scal(3) will now be an int array.

Functional programming

Functional programming (FP) is now popular in emerging programming languages and is being added to those that are already established. Not only does D have FP support, it is the only major programming language where purity in functions can be optionally enforced (in the case of D by using the pure keyword). The FP version of the scal function is:

import std.algorithm.iteration: sum, map;
import std.array: array;

X[] scal(X)(X[] x, X a)
{
    return x.map!((xi) => xi*a).array();
}

The functional version of dot:

import std.stdio : writeln;
import std.range : zip;
import std.algorithm.iteration: sum, map;

X dot(X)(X[] x, X[] y)
{
    return zip(x, y).map!(a => a[0]*a[1]).sum;
}

void main(){
    double[] x = [1, 2, 3, 4, 5, 6];
    double[] y = [3, 6, 9, 12, 15, 18];
    writeln(dot(x, y));
}

C-Style Programming

D also has C-style syntax (in fact D is fully compatible with C and can call and can be called from C). The scal function in pointer style is given below:

import std.stdio : writeln;

void scal(N, X)(N n, in X a, X* x)
{
    while(n)
    {
        *x *= a;
        x += 1;
        --n;
    }
}

void main(){
    double[] x = [1, 2, 3, 4, 5, 6];
    scal(6, 3, x.ptr);
    writeln(x);
}

In D the .ptr append returns a pointer to the array.

Summary

From this article, it should be clear that D is not dogmatic but rather quite accepting of different programming paradigms and it has a flexible syntax. This blog post did not touch on some of the programming approaches available in D for instance objects and inheritance, however the standard library and language reference is available on the D Programming Language official website and Ali Çehreli’s Programming in D book is a fantastic resource for learning how to program in D.