After the success of the last article detailing hidden treasures in the D standard library, I thought I would write another highlighting why the D programming language coupled with its great standard library is surprisingly useful. The library itself is a vast beast and has been written by some exceptional programmers, so occasionally you stumble across some truly useful and well designed nuggets of code. This article shows a few more of these hidden treasures and provides examples of where they could be useful when used in your projects.
The following code examples make good use of D’s uniform function call syntax and template metaprogramming capabilities. Don’t be put off by this as very simple explanations for these can be found here and here.
This package is huge and implements generic algorithms oriented towards the processing of sequences. Sequences processed by these functions (for the most part) define range based interfaces.
This function returns one of a defined collection of expressions based on the value of a switch expression. While this sounds quite complicated, here’s a simple example implementing the standard fizzbuzz program.
predSwitch function takes one template parameter, which defines a predicate. This predicate is used to choose which expression to return from the function. The first function parameter is a switch expression, which will replace the
a in the predicate. (This is shown in the above example as the
input variable using UFCS). The next parameters are variadic and define pairs of test expressions and return expressions. In the above code
15 will replace the
b in the predicate, while its pair
"fizzbuzz" defines the return expression if the predicate evaluates to true depending on the value of
input. Many pairs can be defined and the last parameter is the default return expression if there are no matches.
At first glance it looks like it’s just a reimplementation of an if or switch statement until you realise the function parameters can actually be expressions and all variadic arguments are evaluated lazily. This helps write cleaner code where before it would be littered with conditional statements. View Documentation.
This template is used for the creation of struct or class fields while controlling how many bits each field uses. Using this template, you specify the types, names and bit sizes of each field as parameters. The total bit size must be equal to the size of a built-in integer type, which is used as the underlying data store. During compile time, code is generated by the template which can in-turn be mixed-in to be compiled into the executable. Take a look at the following code.
foo is only two bytes in size but contains many fields, all utilising the same short for storage and providing access to a subset of bits. Defining structs like this can be very useful to define programmer friendly ‘views’ into binary data. View Documentation.
This module implements functions for conversion between values and types. At first glance, there doesn’t seem to be a lot in here but what is here is very comprehensive.
This template is a simple wrapper over the standard cast operator, but its perceived redundancy is more than made up for by its usefulness. For example, when casting a value to another type, for reasons of safety and legality you need to know and verify what type that value is before the cast. Once the cast is made, the code from then on usually assumes the source type will never change. This template makes this assumption an explicit rule to avoid potential bugs from occurring. Take a look at the following snippet.
This code makes an assumption that the
foo variable will always be an
int. If we change
foo to be a pointer, everything will still compile fine but the program is obviously bugged. Here is another snippet showing how you can avoid situations like this by safely enforcing the source type.
This module implements functions that manipulate other functions. I guess this is where D implements library functions to accommodate some aspects of functional programming. It’s a bit thin on the ground in there but what is there seems to be very useful.
This template composes functions together to create a chain. In this chain the result of each composed function is passed as an argument to the next and the result of the last one is the result of the whole. Here is an example.
Here we are composing various built-in library functions to create a function called
sumString. This function when called will chain the functions as follows.
This is great for creating complicated functions by composing existing ones in clever ways. View Documentation.
compose template functions in exactly the same way as the
pipe template with the exception that the arguments passed are defined in the opposite order. This is purely for readability cases. Here is the above example now using
Notice the order of the arguments when using
compose. View Documentation.
This module defines the notion of a range. Ranges generalise the concept of arrays, lists, or anything that involves sequential access. This abstraction enables the same set of algorithms to be used with a vast variety of different concrete types.
This is a function that when passed a callable argument will create an infinite InputRange whose
front method returns the value from successive calls to the argument. This is especially useful when using a function with global side effects i.e. random functions, or to create ranges expressed as a single delegate, rather than defining the entire
empty interface manually. Here’s an example.
The above code generates a range from the
randNum function and prints three elements from it. The returned range can be considered lazy because the values are only generated as they are needed. In the above case they are only generated when we call
take(3). During generation,
front is called on the range which in turn then calls
randNum. The returned range can also be considered infinite, because it could continue to keep generating values forever.
There is however a problem with this range. Ranges should always return the same value for successive calls to
front, this one doesn’t because it calls
randNum each time. To achieve the desired behaviour we can leverage another function from a different package
std.algorithm.iteration.cache. This function can be chained onto the returned range to cache the value of
front without needing to call it again.
cache eagerly evaluates
front on each range construction or call to
popFront, to store the result in a cache. The result is then directly returned when
front is called, rather than re-evaluated. Here is an amended example.
Now successive calls to the range’s
front method always return the same cached value, which also means it’s now semantically correct and more performant. View Documentation.
This module implements a variety of type constructors, i.e. templates that allow construction of new, useful general purpose types.
This struct enables you to encapsulate unique ownership of a particular resource. Once used, the resource is deleted at the end of the current scope unless it’s transferred. A transfer can be explicit, by calling the release method, or implicit, when returning the struct from a function. The following code shows an example of how to use it.
The above example shows how a unique resource is created, borrowed by reference and then ownership transferred to a function by calling
isEmpty is used to determine whether a resource is encapsulated in the struct. View Documentation.
D provides an absolute wealth of fantastic features and advanced ideas in the standard library. Featured above are only a few of the treasures to be found. Click the documentation links above and read more about each one and see for yourself the appeal and productivity of a modern system programming language such as D.