diff --git a/basics/metaprogramming1.md b/basics/metaprogramming1.md new file mode 100644 index 0000000..e82cfec --- /dev/null +++ b/basics/metaprogramming1.md @@ -0,0 +1,173 @@ +# Metaprogramming in D, Part 1 + +## AliasSeq!(T) compile time sequences + +`AliasSeq!(T)` is implemented in the `std.meta` library of Phobos; it allows us to create compile time sequences of types and data. Its implementation is simple: + + alias AliasSeq(T...) = T; + +The `Nothing` template is also defined in the same module: + + alias Nothing = AliasSeq!(); + +We can create a compile time sequence like this: + + alias tSeq = AliasSeq!(ushort, short, uint, int, ulong, long); + pragma(msg, "Type sequence: ", tSeq); + +output: + + Type sequence: (ushort, short, uint, int, ulong, long) + +### Append, prepend and concatenating compile time lists + +`AliasSeq(T...)` type sequences are not a traditional "container"; when they are input into templates they "spread out", or expand and become like separate arguments of a function, as if they were not contained but input individually. One consequence is that there is no need to define operations for Append, Prepend, and Concatenate because they is already implied in the definition. The following examples show this applied. + +#### Append + +Here, we append `string` to the end of our type sequence: + + alias appended = AliasSeq!(tSeq, string); + pragma(msg, appended); + + +output: + + Append: (ushort, short, uint, int, ulong, long, string) + + +#### Prepend + +Here, we prepend `string` to the front of our type sequence: + + alias appended = AliasSeq!(string, tSeq); + pragma(msg, "Append: ", appended); + + +Output: + + Prepend: (string, ushort, short, uint, int, ulong, long) + +#### Concatenate + +Here, we concatenate `AliasSeq!(ubyte, byte)` with our original type sequence: + + alias bytes = AliasSeq!(ubyte, byte); + alias concat = AliasSeq!(bytes, tSeq); + pragma(msg, "Concatenate: ", concat); + +Output: + + Concatenate: (ubyte, byte, ushort, short, uint, int, ulong, long) + +## Replacing items in compile time sequences + +We can manipulate compile time sequences, and some *functional* modes of manipulation are given in the [std.meta](https://dlang.org/phobos/std_meta.html) module. Below, a template expression is implemented that allows us to replace an item in a compile time sequence `T...`, with a type `S`: + + template Replace(size_t idx, S, Args...) + { + static if (Args.length > 0 && idx < Args.length) + alias Replace = AliasSeq!(Args[0 .. idx], S, Args[idx + 1 .. $]); + else + static assert(0); + } + +## Replacing multiple items with an individual type + +### String mixins + +String mixins allow us to create runtime and compile time code by concatenating strings together like C macros. + + +The code below implements a `Replace` template specialization. It uses mixins to create compile time code "on the fly". These commands facilitate multiple replacements at locations in `Args` given by the sequence `indices`: + + import std.conv: text; + template Replace(alias indices, S, Args...) + if(Args.length > 0) + { + enum N = indices.length; + static foreach(i; 0..N) + { + static if(i == 0) + { + mixin(text(`alias x`, i, ` = Replace!(indices[i], S, Args);`)); + }else{ + mixin(text(`alias x`, i, ` = Replace!(indices[i], S, x`, (i - 1), `);`)); + } + } + mixin(text(`alias Replace = x`, N - 1, `;`)); + } + +### static foreach + +In the static foreach loop example above, the variables at each iteration are **not** scoped, because we use single curly braces `{` `}`. This means that all the variables we generate, `x0..x(N-1)` are all available outside these braces at the last line, where we assign the last variable created to `Replace`, to be "returned". To scope variables created in a `static foreach` loops, we use double curly braces `{{` `}}` like this: + + static foreach(i; 0..N) + {{ + /* ... code here ... */ + }} + +## Replacing multiple items by a tuple of items + +### Contained type sequences + +We have already seen that there if we pass more than one `AliasSeq!(T)` sequence as template parameters, the template expands and we can not recover which items were in which parameter. To remedy this, we can build a tuple type as a container for types. For more information see std.typecons which contains tools for interacting with tuples: + + struct Tuple(T...) + { + enum ulong length = T.length; + alias get(ulong i) = T[i]; + alias getTypes = T; + } + +We are now ready for a template specialization that replaces multiple items in `AliasSeq` by the types in a tuple: + + template Replace(alias indices, S, Args...) + if((Args.length > 0) && isTuple!(S) && (indices.length == S.length)) + { + enum N = indices.length; + static foreach(i; 0..N) + { + static if(i == 0) + { + mixin(text(`alias x`, i, ` = Replace!(indices[i], S.get!(i), Args);`)); + }else{ + mixin(text(`alias x`, i, + ` = Replace!(indices[i], S.get!(i), x`, (i - 1), `);`)); + } + } + mixin(text(`alias Replace = x`, N - 1, `;`)); + } + +Usage: + + alias replace6 = Replace!([0, 2, 4], Tuple!(long, ulong, real), tSeq); + pragma(msg, "([0, 2, 4]) => (long, ulong, real): ", replace6); + + +Output: + + ([0, 2, 4]) => (long, ulong, real): (long, short, ulong, int, real, long) + +## {SourceCode} +```d +import std.conv: to; +import std.stdio: writeln; + +struct Tuple(T...) +{ + enum length = T.length; + alias data = T; + auto opIndex()(long i) + { + return data[i]; + } +} + +void main() +{ + alias tup = Tuple!(2020, "August", "Friday", 28).data; + writeln(tup[2] ~ ", " ~ to!(string)(tup[3]) ~ ", " ~ tup[1] ~ + " " ~ to!(string)(tup[0])); +} +``` diff --git a/basics/metaprogramming2.md b/basics/metaprogramming2.md new file mode 100644 index 0000000..292fe58 --- /dev/null +++ b/basics/metaprogramming2.md @@ -0,0 +1,109 @@ +# Metaprogramming in D, Part 2 + +## Template mixins, CTFE, and import + +### Template mixins + +A template `mixin` allows code blocks to be inserted at any relevant point in our script. Consider the following code using our previous kernel functions, but this time they are expressed as template mixins. We can choose which kernel function to compile by selecting the relevant `mixin`: + + mixin template dot() + { + auto kernel(T)(T[] x, T[] y) + if(is(T == float) || is(T == double) || is(T == real)) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + } + + mixin template gaussian() + { + import std.math: exp, sqrt; + auto kernel(T)(T[] x, T[] y) + if(is(T == float) || is(T == double) || is(T == real)) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + auto tmp = x[i] - y[i]; + dist += tmp * tmp; + } + return exp(-sqrt(dist)); + } + } + + mixin gaussian!();//chosing the compile the gaussian kernel + +### CTFE + +Compile Time Function Evaluation (CTFE) is a feature where a function can be evaluated at compile time, either explicitly by making its output an enumeration, or implicitly by the compiler opting to evaluate an immutable variable at compile time for efficiency purposes. Following from the mixins declared above we can use CTFE to calculate a value for the `kernel`. + + //Inputs available at compile time + enum x = [1., 2, 3, 4]; + enum y = [4., 3, 2, 1]; + // CTFE + enum calc = kernel!(double)(x, y); + //Output + pragma(msg, "kernel: ", calc); + +### import("code.d") + +If we put the template mixin code and CTFE evaluations above into a file named `code.d`, we could load them all in another D module at compile time by using the `import` directive: + + //myModule.d + enum code = import("code.d"); + mixin(code); + +Now compile: + + dmd myModule.d -J="." -o- + +In this case we add the `-o-` flag because we don't use the `main` function in the scripts (there's no runtime execution). The `-J` flag must be used when we import in this way. It is used to specify a relative path to where the `code.d` file is located. + +## {SourceCode} +```d +mixin template dot() +{ + auto kernel(T)(T[] x, T[] y) + if(is(T == float) || is(T == double) || is(T == real)) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } +} + +mixin template gaussian() +{ + import std.math: exp, sqrt; + auto kernel(T)(T[] x, T[] y) + if(is(T == float) || is(T == double) || is(T == real)) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + auto tmp = x[i] - y[i]; + dist += tmp * tmp; + } + return exp(-sqrt(dist)); + } +} + +mixin gaussian!(); + +enum x = [1., 2, 3, 4]; +enum y = [4., 3, 2, 1]; +enum calc = kernel!(double)(x, y); +pragma(msg, "kernel: ", calc); +``` diff --git a/basics/template2.md b/basics/template2.md new file mode 100644 index 0000000..b793e46 --- /dev/null +++ b/basics/template2.md @@ -0,0 +1,224 @@ +# Templates in D, Part 2 + +## Struct, class, and interface templates + +The eponymous `struct` declaration: + + template Data(T) + { + struct Data + { + T[] x; + T[] y; + } + } + +In the same way that function templates with different parameters, are of different types, structs with different templates parameters, also have different types. This means that `is(Data!(double) == Data!(float))` is `false`. + +The above template is an eponymous template, meaning that one of its members, has the same name as the template itself. This is in contrast to the template below: + + template LineSegment(T) + { + struct Point + { + T x; + T y; + T z; + } + struct Line + { + Point from; + Point to; + } + } + +usage: + + alias F = double; + alias ls = LineSegment!(F); + auto from = ls.Point(0., 0., 0.); + auto to = ls.Point(1., 1., 1.); + auto line = ls.Line(from, to); + writeln("Line: ", line); + + +Class and interface templates, are declared in the same way as struct templates: + + import std.stdio: writeln; + enum double pi = 3.14159265359; + + interface Shape(T) + { + string name(); + T volume(); + } + + class Box(T): Shape!(T) + { + T length; + T width; + T height; + this(T length, T width, T height) + { + this.length = length; + this.width = width; + this.height = height; + } + this(T length) + { + this.length = length; + this.width = length; + this.height = length; + } + T volume() + { + return length * width * height; + } + string name() + { + return "Box"; + } + } + class Sphere(T): Shape!(T) + { + T radius; + this(T radius) + { + this.radius = radius; + } + T volume() + { + return (4/3)*pi*(radius^^3); + } + string name() + { + return "Sphere"; + } + } + class Cylinder(T): Shape!(T) + { + T radius; + T height; + this(T radius, T height) + { + this.radius = radius; + this.height = height; + } + T volume() + { + return pi*height*(radius^^2); + } + string name() + { + return "Cylinder"; + } + } + +Note how the parameter (`T`) of the interface template, is the same as in the subclasses, for example `class Cylinder(T): Shape!(T){\*... Code ...*\}`. This **must** be the case in this instance. We can **not** have `class Cylinder(T): Shape!(U){\*... Code ...*\}`, where `is(T == U)` is `false`, because the signatures of the methods defined in the interface, will not match their implementation in the subclasses. So care must be taken, when implementing inheritance patterns, for interface and class templates. + + alias F = double; + Shape!(F) box = new Box!(F)(2., 3., 4.); + Shape!(F) cube = new Box!(F)(2.); + Shape!(F) ball = new Sphere!(F)(5); + Shape!(F) tube = new Cylinder!(F)(3, 6); + Shape!(F)[] shapes = [box, cube, ball, tube]; + foreach(shape; shapes) + { + writeln("Shape: ", shape.name, ", volume: ", shape.volume); + } + +output: + + + Shape: Box, volume: 24 + Shape: Box, volume: 8 + Shape: Sphere, volume: 392.699 + Shape: Cylinder, volume: 169.646 + +## {SourceCode} +```d +import std.stdio: writeln; +enum double pi = 3.14159265359; + +interface Shape(T) +{ + string name(); + T volume(); +} + +class Box(T): Shape!(T) +{ + T length; + T width; + T height; + this(T length, T width, T height) + { + this.length = length; + this.width = width; + this.height = height; + } + this(T length) + { + this.length = length; + this.width = length; + this.height = length; + } + T volume() + { + return length * width * height; + } + string name() + { + return "Box"; + } +} +class Sphere(T): Shape!(T) +{ + T radius; + this(T radius) + { + this.radius = radius; + } + T volume() + { + return (4/3)*pi*(radius^^3); + } + string name() + { + return "Sphere"; + } +} +class Cylinder(T): Shape!(T) +{ + T radius; + T height; + this(T radius, T height) + { + this.radius = radius; + this.height = height; + } + T volume() + { + return pi*height*(radius^^2); + } + string name() + { + return "Cylinder"; + } +} + +void main() +{ + alias F = double; + Shape!(F) box = new Box!(F)(2., 3., 4.); + Shape!(F) cube = new Box!(F)(2.); + Shape!(F) ball = new Sphere!(F)(5); + Shape!(F) tube = new Cylinder!(F)(3, 6); + Shape!(F)[] shapes = [box, cube, ball, tube]; + foreach(shape; shapes) + { + writeln("Shape: ", shape.name, ", volume: ", shape.volume); + } +} +``` + diff --git a/basics/template3.md b/basics/template3.md new file mode 100644 index 0000000..c92114b --- /dev/null +++ b/basics/template3.md @@ -0,0 +1,110 @@ +# Templates in D, Part 3 + +## Variadic template objects + +In the same way as in function templates, variadic templates can be used in structs, classes, and interfaces. An example of a variadic struct is given below. The implementation for classes and interfaces follow the same pattern: + + import std.conv: to; + import std.stdio: writeln; + + struct Tuple(T...) + { + enum length = T.length; + alias data = T; + auto opIndex()(long i) + { + return data[i]; + } + } + + void main() + { + alias tup = Tuple!(2020, "August", "Friday", 28).data; + writeln(tup[2] ~ ", " ~ to!(string)(tup[3]) ~ ", " ~ tup[1] ~ + " " ~ to!(string)(tup[0])); + } + +## Traits + +`pragma(msg, "...")` is a compile time directive for printing messages. Enumerations are templatable, they are a great way of defining expressions in compile time predicates, for template constraints. These types of expressions, are used extensively in the [std.traits](https://dlang.org/phobos/std_traits.html) standard library. We can combine these predicate expressions *traits*, to form other expressions: + + enum isSignedInteger(T) = is(T == short) || is(T == int) || is(T == long); + enum isUnsignedInteger(T) = is(T == ushort) || is(T == uint) || is(T == ulong); + enum isInteger(T) = isSignedInteger!(T) || isUnsignedInteger!(T); + enum isFloat(T) = is(T == float) || is(T == double) || is(T == real); + enum isNumeric(T) = isInteger!(T) || isFloat!(T); + +These compile time expressions, can be used in template constraints with the `if()` directive. An example for template functions: + + template dot(T) + if(isFloat!(T))// template constraint is here + { + auto dot(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + } + +## static if + +The `static if` directive allows us to carry out conditional compilation. The code in its scope is compiled if the condition is met, otherwise other commands can be executed, or further conditions given. This means that if the condition is not met, the respective code in scope is not compiled. + +Recall the previous example for the `dot` kernel function template but this time with possiby different type array inputs: + + template dot(T, U) + if(isFloat!(T) && isFloat!(U)) + { + auto dot(T[] x, U[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + } + + +The problem is that we don't really know if the return type `T`, is more suitable than type `U`. We would like to return which ever value is most accurate, meaning is the larger float type. + +We can use `static if` with `alias` to create a `promote` template expression: + + template promote(T, U) + { + static if(T.sizeof > U.sizeof) + alias promote = T; + else + alias promote = U; + } + +We can apply it to our template function: + + template isFloat(T) + { + enum isFloat = is(T == float) || is(T == double) || is(T == real); + } + + template dot(T, U) + if(isFloat!(T) && isFloat!(U)) + { + auto dot(T[] x, U[] y) + { + promote!(T, U) dist = 0; // result type is now defined by promote + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + } + + diff --git a/basics/templates.md b/basics/templates.md deleted file mode 100644 index 3529f78..0000000 --- a/basics/templates.md +++ /dev/null @@ -1,105 +0,0 @@ -# Templates - -**D** allows defining templated functions similar to C++ and Java -which is a means to define **generic** functions or objects which work -for any type that compiles with the statements within the function's body: - - auto add(T)(T lhs, T rhs) { - return lhs + rhs; - } - -The template parameter `T` is defined in a set of parentheses -in front of the actual function parameters. `T` is a placeholder -which is replaced by the compiler when actually *instantiating* -the function using the `!` operator: - - add!int(5, 10); - add!float(5.0f, 10.0f); - add!Animal(dog, cat); // won't compile; Animal doesn't implement + - -### Implicit Template Parameters - -Function templates have two parameter sets - the first is for -compile-time arguments and the second is for run-time arguments. -(Non-templated functions can accept only run-time arguments). -If one or more compile-time arguments are left unspecified when the function is called, -the compiler tries to deduce them from the list of run-time arguments as the types of those arguments. - - int a = 5; int b = 10; - add(a, b); // T is to deduced to `int` - float c = 5.0; - add(a, c); // T is deduced to `float` - -### Template properties - -A function can have any number of template parameters which -are specified during instantiation using the `func!(T1, T2 ..)` -syntax. Template parameters can be of any basic type -including `string`s and floating point numbers. - -Unlike generics in Java, templates in D are compile-time only, and yield -highly optimized code tailored to the specific set of types -used when actually calling the function - -Of course, `struct`, `class` and `interface` types can be defined as template -types too. - - struct S(T) { - // ... - } - -### In-depth - -- [Tutorial to D Templates](https://github.com/PhilippeSigaud/D-templates-tutorial) -- [Templates in _Programming in D_](http://ddili.org/ders/d.en/templates.html) - -#### Advanced - -- [D Templates spec](https://dlang.org/spec/template.html) -- [Templates Revisited](http://dlang.org/templates-revisited.html): Walter Bright writes about how D improves upon C++ templates. -- [Variadic templates](http://dlang.org/variadic-function-templates.html): Articles about the D idiom of implementing variadic functions with variadic templates - -## {SourceCode} - -```d -import std.stdio : writeln; - -/** -Template class that allows -generic implementation of animals. -Params: - noise = string to write -*/ -class Animal(string noise) { - void makeNoise() { - writeln(noise ~ "!"); - } -} - -class Dog: Animal!("Woof") { -} - -class Cat: Animal!("Meeoauw") { -} - -/** -Template function which takes any -type T that implements a function -makeNoise. -Params: - animal = object that can make noise - n = number of makeNoise calls -*/ -void multipleNoise(T)(T animal, int n) { - for (int i = 0; i < n; ++i) { - animal.makeNoise(); - } -} - -void main() { - auto dog = new Dog; - auto cat = new Cat; - multipleNoise(dog, 5); - multipleNoise(cat, 5); -} -``` diff --git a/basics/templates1.md b/basics/templates1.md new file mode 100644 index 0000000..2dc4aab --- /dev/null +++ b/basics/templates1.md @@ -0,0 +1,199 @@ +# Templates in D, Part 1 + +## Function templates + +### Template longhand and shorthand declarations + +There are two ways of declaring function templates. The longhand template notation, for example: + + template dot(T) + { + auto dot(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + } + +The shorthand notation for this same template is: + + auto dot(T)(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + +Shorthand templates are *always* eponymous. + +Template functions are used with the form `dot!(T...)(T args)` for example: + + import std.stdio: writeln; + void main() + { + auto x = [1., 2, 3, 4]; + auto y = [4., 3, 2, 1]; + writeln("dot product: ", dot!(double)(x, y)); + } + +### Access patterns for template internals + +Functions contained within a template, can have a different name from the template itself. For example: + + template Kernel(T) + { + auto dot(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + import std.math: exp, sqrt; + auto gaussian(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + auto tmp = x[i] - y[i]; + dist += tmp * tmp; + } + return exp(-sqrt(dist)); + } + } + +We access the functions in the template like this: + + writeln("gaussian kernel: ", Kernel!(double).gaussian(x, y)); + +### Variadic template functions + +Variadic template functions in D, are very similar in design to those in C++. The type sequence, is specified with an ellipsis `T...`. The template function below, can sum a variable number of inputs, each potentially of a different type: + + auto sum(T)(T x) + { + return x; + } + auto sum(F, T...)(F first, T tail) + { + return first + sum!(T)(tail); + } + +Usage: + + auto num = sum(1, 2.3f, 9.5L, 5.7); + +### The `is()` directive + +`is()` is a compile time directive, that converts expressions on types, into boolean constants. + + template dot(T) + if(is(T == float) || is(T == double) || is(T == real)) + { + auto dot(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + } + +the expression `if(is(T == float) || is(T == double) || is(T == real))`, *constrains* the template to be valid for conditions were `T` is one of `float`, `double`, or `real`. + +## {SourceCode} +```d +template dot(T) +if(!(is(T == float) || is(T == double) || is(T == real))) +{ + static assert(false, "Type: " ~ T.stringof ~ " not value for the dot function"); +} + +template dot(T) +if(is(T == float) || is(T == double) || is(T == real)) +{ + auto dot(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } +} + +template Kernel(T) +{ + auto dot(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + dist += x[i] * y[i]; + } + return dist; + } + import std.math: exp, sqrt; + auto gaussian(T[] x, T[] y) + { + T dist = 0; + auto m = x.length; + for(size_t i = 0; i < m; ++i) + { + auto tmp = x[i] - y[i]; + dist += tmp * tmp; + } + return exp(-sqrt(dist)); + } +} + +enum isFloat(T) = is(T == float) || is(T == double) || is(T == real); +template promote(T, U) +if(isFloat!T && isFloat!U) +{ + static if(T.sizeof > U.sizeof) + alias promote = T; + else + alias promote = U; +} + +auto sum(T)(T x){ + return x; +} +auto sum(F, T...)(F first, T tail) +{ + return first + sum!(T)(tail); +} + +import std.stdio: writeln; +void main() +{ + double[4] x = [1., 2, 3, 4]; + double[4] y = [4., 3, 2, 1]; + writeln("dot product: ", dot!(double)(x, y) ); + + writeln("promote: ", promote!(real, double).stringof); + auto num = sum(1, 2.3f, 9.5L, 5.7); + writeln("Sum: ", num); + writeln("Sum: ", typeof(num).stringof); +} +```