1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
---
language: D
filename: learnd.d
contributors:
- ["Nick Papanastasiou", "www.nickpapanastasiou.github.io"]
lang: en
---
```d
// You know what's coming...
module hello;
import std.stdio;
// args is optional
void main(string[] args) {
writeln("Hello, World!");
}
```
If you're like me and spend way too much time on the internet, odds are you've heard
about [D](http://dlang.org/). The D programming language is a modern, general-purpose,
multi-paradigm language with support for everything from low-level features to
expressive high-level abstractions.
D is actively developed by a large group of super-smart people and is spearheaded by
[Walter Bright](https://en.wikipedia.org/wiki/Walter_Bright) and
[Andrei Alexandrescu](https://en.wikipedia.org/wiki/Andrei_Alexandrescu).
With all that out of the way, let's look at some examples!
```d
import std.stdio;
void main() {
// Conditionals and loops work as expected.
for(int i = 0; i < 10000; i++) {
writeln(i);
}
// 'auto' can be used for inferring types.
auto n = 1;
// Numeric literals can use '_' as a digit separator for clarity.
while(n < 10_000) {
n += n;
}
do {
n -= (n / 2);
} while(n > 0);
// For and while are nice, but in D-land we prefer 'foreach' loops.
// The '..' creates a continuous range, including the first value
// but excluding the last.
foreach(n; 1..1_000_000) {
if(n % 2 == 0)
writeln(n);
}
// There's also 'foreach_reverse' when you want to loop backwards.
foreach_reverse(n; 1..int.max) {
if(n % 2 == 1) {
writeln(n);
} else {
writeln("No!");
}
}
}
```
We can define new types with `struct`, `class`, `union`, and `enum`. Structs and unions
are passed to functions by value (i.e. copied) and classes are passed by reference. Furthermore,
we can use templates to parameterize all of these on both types and values!
```d
// Here, 'T' is a type parameter. Think '<T>' from C++/C#/Java.
struct LinkedList(T) {
T data = null;
// Use '!' to instantiate a parameterized type. Again, think '<T>'.
LinkedList!(T)* next;
}
class BinTree(T) {
T data = null;
// If there is only one template parameter, we can omit the parentheses.
BinTree!T left;
BinTree!T right;
}
enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
// Use alias to create abbreviations for types.
alias IntList = LinkedList!int;
alias NumTree = BinTree!double;
// We can create function templates as well!
T max(T)(T a, T b) {
if(a < b)
return b;
return a;
}
// Use the ref keyword to ensure pass by reference. That is, even if 'a' and 'b'
// are value types, they will always be passed by reference to 'swap()'.
void swap(T)(ref T a, ref T b) {
auto temp = a;
a = b;
b = temp;
}
// With templates, we can also parameterize on values, not just types.
class Matrix(uint m, uint n, T = int) {
T[m] rows;
T[n] columns;
}
auto mat = new Matrix!(3, 3); // We've defaulted type 'T' to 'int'.
```
Speaking of classes, let's talk about properties for a second. A property
is roughly a function that may act like an lvalue, so we can
have the syntax of POD structures (`structure.x = 7`) with the semantics of
getter and setter methods (`object.setX(7)`)!
```d
// Consider a class parameterized on types 'T' & 'U'.
class MyClass(T, U) {
T _data;
U _other;
}
// And "getter" and "setter" methods like so:
class MyClass(T, U) {
T _data;
U _other;
// Constructors are always named 'this'.
this(T t, U u) {
// This will call the setter methods below.
data = t;
other = u;
}
// getters
@property T data() {
return _data;
}
@property U other() {
return _other;
}
// setters
@property void data(T t) {
_data = t;
}
@property void other(U u) {
_other = u;
}
}
// And we use them in this manner:
void main() {
auto mc = new MyClass!(int, string)(7, "seven");
// Import the 'stdio' module from the standard library for writing to
// console (imports can be local to a scope).
import std.stdio;
// Call the getters to fetch the values.
writefln("Earlier: data = %d, str = %s", mc.data, mc.other);
// Call the setters to assign new values.
mc.data = 8;
mc.other = "eight";
// Call the getters again to fetch the new values.
writefln("Later: data = %d, str = %s", mc.data, mc.other);
}
```
With properties, we can add any amount of logic to
our getter and setter methods, and keep the clean syntax of
accessing members directly!
Other object-oriented goodies at our disposal
include interfaces, abstract classes,
and overriding methods. D does inheritance just like Java:
Extend one class, implement as many interfaces as you please.
We've seen D's OOP facilities, but let's switch gears. D offers
functional programming with first-class functions, `pure`
functions, and immutable data. In addition, all of your favorite
functional algorithms (map, filter, reduce and friends) can be
found in the wonderful `std.algorithm` module!
```d
import std.algorithm : map, filter, reduce;
import std.range : iota; // builds an end-exclusive range
void main() {
// We want to print the sum of a list of squares of even ints
// from 1 to 100. Easy!
// Just pass lambda expressions as template parameters!
// You can pass any function you like, but lambdas are convenient here.
auto num = iota(1, 101).filter!(x => x % 2 == 0)
.map!(y => y ^^ 2)
.reduce!((a, b) => a + b);
writeln(num);
}
```
Notice how we got to build a nice Haskellian pipeline to compute num?
That's thanks to a D innovation know as Uniform Function Call Syntax (UFCS).
With UFCS, we can choose whether to write a function call as a method
or free function call! Walter wrote a nice article on this
[here.](http://www.drdobbs.com/cpp/uniform-function-call-syntax/232700394)
In short, you can call functions whose first parameter
is of some type A on any expression of type A as a method.
I like parallelism. Anyone else like parallelism? Sure you do. Let's do some!
```d
// Let's say we want to populate a large array with the square root of all
// consecutive integers starting from 1 (up until the size of the array), and we
// want to do this concurrently taking advantage of as many cores as we have
// available.
import std.stdio;
import std.parallelism : parallel;
import std.math : sqrt;
void main() {
// Create your large array
auto arr = new double[1_000_000];
// Use an index, access every array element by reference (because we're
// going to change each element) and just call parallel on the array!
foreach(i, ref elem; parallel(arr)) {
elem = sqrt(i + 1.0);
}
}
```
|