Dive into D
Dmitry Olshansky
What is D ?
C-style
System-level language
With (optional) GC
that aims to be...
Safer, Simpler, Better then C++
Timeline
2000
2016
2007
Walter Bright
compiler expert
Andrei Alexandescu
C++ guru
Joins
Timeline
2000
2016
2007
Walter Bright
C++ compiler expert
Andrei Alexandescu
C++ guru
Joins
- Slices
- AAs (maps)
- Unittests
- Fibers (~ coroutines)
- Closures
- Directly calls to C
- Metaprograming
- Generative programming
- Immutability
- Purity
- Thread local by default
Show me the code!
#!/usr/bin/rdmd
import std.stdio;
void main()
{
writeln("Hello, world!");
}
extern(C) int printf(const(char)* str, ...);
void main()
{
printf("Hello, world!\n");
}
The C-style counterpart
Simple, correct and even scriptable!
Safe and easy
Slices
void main(){
// slice is dynamic array on GC heap
int[] slice = [1, 2, 3, 4, 5];
// slice the range of [1:3)
int[] a = slice[1..4];
assert(a == [2,3,4]);
a ~= 6; // append 6
assert(a == [2,3,4,6]);
// no stomping!
assert(slice == [1,2,3,4,5]);
// add 2 arrays as vectors
a[] += slice[1..$]; // $ is slice.length
assert(a == [4,6,8,11]);
int[] b = a.dup; // duplicate (=copy)
b[0] = 10;
assert(a[0] == 4);
assert(*a.ptr == 4);
int k = 1;
int[] hacky = (&k)[0..1];
assert(hacky[0] == 1);
hacky ~= 2;
assert(hacky.ptr != &k); //reallocated
assert(hacky == [1, 2]);
}
Slices are views of arrays: pair of length + pointer
Length
Pointer
Builtin AAs
void main() {
double[string] prices = [
"Kid bike": 540.0,
"iPhone": 3000.0,
"Red running shoes": 9999.0
];
if(auto p = "Red running shoes" in prices){
writeln(*p); // prints 9999.0
}
assert("iPad" !in prices);
prices["Kid bike"] += 160;
assert(prices["Kid bike"] == 700);
}
AA - associative array, a key-value hash map:
- any hashable type as key
- any type as value
UFCS
Uniform Function Call Syntax
Any free function can be called with method syntax if the first argument is of the compatible type
Natural extension of foreign types
auto square(int x){
return x*x;
}
auto half(double x){
return x/2.0;
}
unittest { //built-in unittest
// can import in local scope
import std.conv;
assert(4.square == 16);
// to is a free (template) function from std.conv
assert(45.to!string == "45");
assert(31.half == 15.5);
}
Numeric literals
int number = 1000000;
//C++14 only(!)
unsigned int bitmask = 0b11010110;
unsigned int hex = 0xDEADBEAF
int number = 1_000_000;
uint bitmask = 0b1101_0110;
uint hex = 0xDEAD_BEAF;
C++
D
Was that 5 zeros or 6 zeros?
Error elimination
void clubEntrance(int age, string state)
{
// Error: state == "sober" must be parenthesized when next to operator &
if(age > 18 & state == "sober"){
// ...
}
}
void faultyCopy(int[] from, int[] to){
auto len = (from.length, to.length);
// Error: use '{ }' for an empty statement, not a ';'
for(int i=0; i<len; i++);
to[i] = from[i];
}
bool checkLucky(int value, bool lucky)
{
// Warning: switch case fallthrough - use 'goto case;' if intended
switch(value){
case 7:
lucky = true;
case 13:
lucky = false;
default:
}
return lucky;
}
Powerful
The power and perils of code reuse!
Generic code
Find most general solution of the problem:
Narrowest requirements
Widest guarantees
Should not need to regress to hand-written code
(as good as hand-written code)
Define types that satisfy requirements
Apply the algorithm
...
Profit!
(Borrowed from Andrei Alexandrescu)
Generic min
import std.traits; // for CommonType
// _Static_ divide and conquer algorithm
// min(a,b,c,d) is min(min(a,b),min(c,d))
CommonType!T min(T...)(T args){
static if(T.length > 2){
// this is static recursion
return min(min(args[0..$/2]), min(args[$/2..$]));
}
else static if(T.length == 2){
return args[0] < args[1] ? args[0] : args[1];
}
else {
return args[0];
}
}
unittest{
assert(min(3,5,-10) == -10);
assert(min( "orange", "pear","apple", "banana") == "apple");
}
Moar code reuse!
void main(){
import std.stdio, std.algorithm, std.range;
stdin
.byLine // get a range of lines
.map!(x => x.idup) // copy each line
.array // allocate as array
.sort!((a,b) => a > b) //sort in reverse order
.each!writeln; // write out each line
}
Rev-sort an input text
With UFCS and the standard library functions
D feels like a high-level functional language...
Compile-time features
int[] partialSums(int[] arr){
int sum;
int[] result = new int[arr.length];
foreach(i,v; arr){
sum += v;
result[i] = sum;
}
return result;
}
//compile time constant
enum table = partialSums([1,2,3,4,5,6,7,8,9]);
static assert(table[2] == 6);
- Can ask compiler to interpret any function at compile time
- Store the result as constant value: primitive, arrays, even structs
Mixins - generative programming
mixin("int a = 3");
mixin(generateMeSomeCode());
mixin - a static version of 'eval' in many script languages
Crude but practical alternative to AST macros
Operator overloading
struct Vector{
double x,y;
auto opBinary(string op)(Vector v)
if(op == "+" || op == "-"){
return mixin("Vector(x "~op~"v.x, y "~op~"v.y)");
}
}
unittest{
auto a = Vector(1,2);
auto b = Vector(3,4);
auto c = a+b;
assert(c.x == 4);
assert(c.y == 6);
}
Operator overloading is actually accessible with operator as static string argument
Useful to define lots of similar operators in bulk
Compile-time regex
unittest{
string phone = "+31 650 903 7158";
auto phoneReg = ctRegex!r"^\+([1-9][0-9]*) [0-9 ]*$";
auto m = match(phone, phoneReg);
assert(m);
assert(m.captures[0] == "+31 650 903 7158");
assert(m.captures[1] == "31");
}
Parse regular expression at compile-time
Generate D source code for matching it
mixin code and package it up to follow the same API
....
Profit!
Scalable
Immutability
Unlike C++ D const and immutable are TRANSITIVE
// string is alias to immutable(char)[]
immutable(char)[] s = "hello";
s[0] = 'H'; // won't compile
// transitive immutability
immutable(char*)** p = ...;
p = ...; // ok, p is not immutable
*p = ...; // ok, *p is not immutable
**p = ...; // error, **p is immutable
***p = ...; // error, ***p is immutable
Transitively immutable ==> thread safe
Can share immutable state freely
Message passing
Milti-threaded file copying with message passing
import std.algorithm, std.concurrency, std.stdio;
void main() {
enum bufferSize = 1024 * 100;
auto tid = spawn(&fileWriter);
// Read loop
foreach (immutable(ubyte)[] buffer; stdin.byChunk(bufferSize)) {
send(tid, buffer);
}
}
void fileWriter() {
// Write loop
for (;;) {
auto buffer = receiveOnly!(immutable(ubyte)[])();
tgt.write(buffer);
}
}
Purity
Usually found in functional languages
A pure function has no observable side effect
Can't access global MUTABLE state
Only call other pure functions
// mutation inside is not observable
// from the outside
pure long factorial(int x){
long ret = 1;
for(int i=2; i<x; i++){
ret *= i;
}
return ret;
}
Weak pure
More pragmatic version - no global state
// weak pure - side effects limited by args
pure int[] partialSum(int[] arr){
int sum;
int[] result = new int[arr.length];
foreach(i,v; arr){
sum += v;
result[i] = sum;
}
return result;
}
// can use inside of strong pure functions
pure int longestZeroSum(const(int)[] arr){
auto partials = partialSum(arr);
int longest = 0;
foreach(i; 0..arr.length)
foreach(j; i..arr.length){
if(partials[i] == partials[j])
longest = max(longest, j - i);
}
return longest;
}
Links
https://dlang.org
http://gdcproject.org
http://wiki.dlang.org/LDC
https://github.com/D-Programming-Language
Dive into D
By Dmitry Olshansky
Dive into D
A short intro to the D programming language
- 1,626