The D Programming Language

advertisement
The D Programming Language
CS 4450
What is D?
A system programming language
conceived in 1996 as a C++ replacement
 Combines the power of C++ with the
usability of languages like Java, C# and
Python
 If something works in C++, C# or Java, it
probably works pretty much the same in
D

“Often, programming teams will resort to a hybrid approach,
where they will mix Python and C++, trying to get the
productivity of Python and the performance of C++. The
frequency of this approach indicates that there is a large unmet
need in the programming language department.”
“D intends to fill that need. It combines the ability to do lowlevel manipulation of the machine with the latest technologies in
building reliable, maintainable, portable, high-level code. D has
moved well ahead of any other language in its abilities to
support and integrate multiple paradigms like imperative, OOP,
and generic programming.”
— Walter Bright, Co-designer of D
D is like C++


It supports static local variables
It supports pointers
◦ but you almost never need them (we won’t)

It has templates
◦ but they’re easier and more flexible than in C++
◦ it supports compile-time execution

It’s fast
◦ compiles to native code

It supports operator overloading
D is Like Java and C#

It supports garbage collection
◦ the default, but you can opt-out

It supports explicit, user-defined interfaces
◦ uses “:” for inheritance, like C#
It has inner classes, like Java and C#
 It has delegates

◦ but more powerful than in C#
It distinguishes reference and value types
 It supports properties

D is Like Python and FP Languages

It has modules and packages
◦ like Python’s

It supports nested functions and closures
◦ including anonymous functions

It has a pure FP subset
◦ and the usual higher-order functions
 reduce, map, filter, compose
◦ supports lazy evaluation of function
parameters
D is for Real Programmers

It has software engineering support:
◦ unit testing
◦ contract programming
 invariants, preconditions, postconditions
◦ built-in constructs for exception-safe
transactions

Supports concurrent programming
◦ with thread-local storage
◦ both lock and lockless programming

Can interoperate with C and C++
D'iving In
// hello.d
import std.stdio;
void main(string[] args) {
if (args.length > 1)
foreach (a; args[1..$])
writeln("Hello " ~ a);
else
writeln("Hello, Modern World");
}
Chuck-Allisons-iMac-4:CCSC chuck$ dmd hello.d
Chuck-Allisons-iMac-4:CCSC chuck$ ./hello
Hello, Modern World
Chuck-Allisons-iMac-4:CCSC chuck$ ./hello John Jane
Hello John
Hello Jane
Primitive Data Types
Type
void
bool
byte
ubyte
short
ushort
int
uint
long
ulong
float
double
real
char
wchar
dchar
Meaning
no value
Boolean value
signed 8 bits
unsigned 8 bits
signed 16 bits
unsigned 16 bits
signed 32 bits
unsigned 32 bits
signed 64 bits
unsigned 64 bits
32-bit floating-point
64-bit floating-point
largest in hardware
unsigned 8-bit UTF-8
unsigned 16-bit UTF-16
unsigned 32-bit UTF-32
Default (.init)
n/a
false
0
0
0
0
0
0
0
0
float. nan
double. nan
real. nan
0xFF
0xFFFF
0x0000FFFF
Literals and Type Inference

Uses the same suffixes as C/C++
◦ f, l, u, etc.

Uses the same prefixes
◦ 0123, 0x1CF
◦ Adds binary: 0b01001

Type inference with auto:
auto targetSalary = 15_000_000;
auto sq = (double x) { return x*x; };
Arrays

Static arrays
◦ Similar to arrays in C/C++
◦ int a[3];
◦ int[3] a;// Same thing

Dynamic Arrays
◦ Similar to vectors, ArrayLists, etc.
See staticarray.d
 Initialization:

◦ int[3] a = [1:2, 3];
// [0,2,3]
Selected Array Operations





Indexing with [ ]
Concatenation with ~
Copying with a.dup
Assignment with =
Length with .length
◦ $ = .length in index expressions (a[2..$])
◦ m..n is an array slice (excludes n)
◦ can assign to .length to resize dynamic arrays


Array-wise operations (mathematical vector
operations)
See mergesort.d, vector.d
Strings

Strings are dynamic arrays of immutable
characters
◦ alias immutable(char)[] string;
◦ string  char
◦ wstring  wchar
◦ dstring  dchar

(UTF-8)
(UTF-16)
(UTF-32)
foreach visits each character:
◦ foreach (c; s) …
Associative Arrays

A map (aka “dictionary”, “hash”, etc.)
◦ Except it’s a built-in type (like dict in Python)

Uses array syntax:
void main() {
int[string] mymap = ["one":1, "two":2];
mymap["three"] = 3;
foreach (k,v; mymap) writef("%s:%s ",k,v);
}
/* Output:
three:3 one:1 two:2
*/
Word Count Program Output
/* Abbreviated output from the Gettysburg Address:
But,: 1
Four: 1
…
who: 3
will: 1
work: 1
world: 1
years: 1
*/
A Word Count Program
import std.stdio, std.array, std.file;
void wc(string filename) {
string[] words =
split(cast(string) read(filename));
int[string] counts;
foreach (word; words)
++counts[word];
foreach (w; counts.keys.sort)
writefln("%s: %d", w, counts[w]);
}
void main(string[] args) {
if (args.length == 2)
wc(args[1]);
}
Tuples
 Defined

in std.typecons
Tuple!(type1,type2,..)
 Helper function:
◦ tuple(val1,val2,…)
 Can
use 0-based position numbers
 Can also use field names
 See tuple.d
Data Qualifiers

For function parameters:
◦ in | out | inout
◦ ref
◦ lazy

General declaration qualifiers:
◦ const
◦ immutable
Lazy Parameters
import std.stdio;
void f(bool b, lazy void g) {
if (b) {
g();
}
}
void main() {
f(false, writeln("executing g"));
f(true, writeln("executing g"));
}
/* Output:
executing g
*/
Closures

Nested functions are returned as
(dynamic) closures
◦ aka “delegates” (a code-environment pair)
◦ The referencing environment could be a
function, class, or object
◦ Escaped activation records are moved from
the stack to the garbage-collected heap

Plain function pointers also supported:
◦ int function(int) f; (vs. “int (*f)(int);” in C)
20
Plain Function Pointers
int f1(int n) {return n+1;}
int f2(int n) {return n+2;}
int f3(int n) {return n+3;}
void main() {
auto funs = [&f1, &f2, &f3];
foreach (f; funs)
writeln(f(1));
}
/* Output:
2
3
4
*/
Higher-Level Functions and Closures
import std.stdio;
bool delegate(int) gtn(int n) {
bool execute(int m) {
return m > n;
}
return &execute;
}
void main() {
auto g5 = gtn(5); // Returns a ">5" delegate
writeln(g5(1)); // false
writeln(g5(6)); // true
}
Lambda Expressions
auto gtn(int n) {
return delegate bool(int m) {return m > n;};
}
// Preferred:
auto gtn(int n) {
return (int m) {return m > n;};
}
An Object Calling Environment
void main() {
class A { int fun() { return 42; } }
A a = new A;
auto dg = &a.fun; // A “bound method”
writeln(dg()); // 42
}
Parametric Polymorphism
Compile-time Parameters
auto gtn(T)(T n) {
return (T m) {return m > n;};
}
void main() {
auto g5 = gtn(5);
writeln(g5(1));
writeln(g5(6));
auto g5s = gtn("baz");
writeln(g5s("bar"));
writeln(g5s("foo"));
}
Compile-Time Constraints
import std.stdio, std.traits;
auto gtn(T)(T n) if (isNumeric!T) {
return (T m) {return m > n;};
}
void main() {
auto g5 = gtn!int(5);
writeln(g5(1));
writeln(g5(6));
auto g5s = gtn!string("baz"); // Error
writeln(g5s("bar"));
writeln(g5s("foo"));
}
Compile-Time Function Evaluation
// ctfe.d: Compile-time function execution
import std.stdio, std.conv;
int square(int i) { return i * i; }
void main() {
static int n = square(3); // compile time
writefln(text(n));
writefln(text(square(4))); // runtime
}
Pure Functions
For Referential Transparency
import std.stdio;
import std.conv;
pure ulong fib(uint n) {
if (n == 0 || n == 1) return n;
ulong a = 1, b = 1;
foreach (i; 2..n) {
auto t = b;
b += a;
a = t;
}
return b;
}
void main(string[] args) {
if (args.length > 1)
writeln(fib(to!(uint)(args[1])));
}
Selected Higher-Level Functions
int[] nums = [1,2,3,4,5];
auto squarelist(int[] list) { // 1 4 9 16 25
return map!("a*a")(list);
}
auto sqsum2(int[] list) {
// 55
return reduce!("a + b*b")(0,list); // (list)
}
auto evens(int[] list) {
// 2 4
return filter!("a % 2 == 0")(list);
}
alias compose!("a/3.0","a*a","a+1.0") comp;
writeln(comp(2.0));
// 3 = (2+1)^2 / 3
alias pipe!(div3,sq,pls1) pip; // fns in scope
writeln(pip(2.0)); // 1.4444 = (2/3)^2+1
std.functional.memoize

Memoization is a technique where a
cache of input-output pairs is kept for
function calls

Avoids recomputing function values

See fib.d
Variable-length Argument Lists
Use “…” in the parameter definition
 Homogeneous

◦ use “…” in the runtime parameter list
◦ passes a dynamic array

Heterogeneous
◦ use “…” in the compile-time parameter list
◦ passes a tuple

See varargs.d
An Unsafe Function
void g() {
risky_op1();
// May acquire resources…
risky_op2();
risky_op3();
writeln("g succeeded");
}
Assume further that these functions must run to
completion or not at all.
An Unsavory Solution
void g() {
risky_op1();
try {
risky_op2();
}
catch (Exception x) {
undo_risky_op1();
// Back-out op1
throw x;
// Rethrow exception
}
try {
risky_op3();
writeln("g succeeded");
}
catch (Exception x) {
// Back-out op1 and op2 in reverse order
undo_risky_op2();
undo_risky_op1();
throw x;
}
}
D’s scope Statement
Options: exit | success | failure
void g() {
risky_op1();
scope(failure) undo_risky_op1();
risky_op2();
scope(failure) undo_risky_op2();
risky_op3();
writeln("g succeeded");
}
Contract Programming Example
Also illustrates value types, operator overloading, and unit testing
import std.math;
// For abs()
struct Rational {
private int num = 0;
private int den = 1;
// Local helper function
static int gcd(int m, int n) {
return n == 0 ? abs(m)
: gcd(abs(n), abs(m)%abs(n));
}
// Class invariant
invariant() {
assert(den > 0 && gcd(num, den) == 1);
}
Contract Programming Example
(continued)
// Constructor
this(int n, int d = 1)
// Constructor precondition
in {
assert(d != 0);
}
body {
num = n;
den = d;
auto div = gcd(num, den);
if (den < 0)
div = -div;
num /= div;
den /= div;
}
Contract Programming Example
(concluded)
// ("if" form evaluated at compile time)
Rational opBinary(string op)(Rational r)
if (op == "+”)
{
return Rational(num*r.den +
den*r.num, den*r.den);
}
}
unittest {
// Compile with –unittest option
auto r1 = Rational(1,2), r2 = Rational(3,4),
r3 = r1 + r2;
assert(r3.num == 5 && r3.den == 4);
}
void main(){}
Variant Types

Sometimes you want to hold any type
◦ hopefully not too often!
Variant can hold any type
 You can only get out what was last put in

◦ see variant.d

Bounded, discriminated unions with
Algebraic
◦ like ML unions
◦ see variant2.d
Threads

A thread is a path of execution inside a
running program (process)

A thread is launched on a function basis

Using the std.concurrency.spawn

See concur1.d
Message Passing
A paradigm for thread-to-thread
communication
Threads send/receive data to/from each
other
Less problematic (and less flexible) than
sharing data via mutexes
See concur1,2,3.d
Infinite Streams in D

Threads and Message Passing naturally
support streams

See stream.d
Download