This shows you the differences between two versions of the page.
dss:laboratoare:06 [2019/07/01 14:00] eduard.staniloiu [static if] |
dss:laboratoare:06 [2019/07/01 15:29] (current) eduard.staniloiu [Exercises] |
||
---|---|---|---|
Line 110: | Line 110: | ||
==== Mixins ==== | ==== Mixins ==== | ||
+ | |||
+ | Mixins are for mixing in generated code into the source code. The mixed in code | ||
+ | may be generated as a template instance or a string . | ||
+ | |||
+ | === Template mixins === | ||
+ | |||
+ | Template mixins insert instantiations of templates into the code by the **mixin** keyword: | ||
+ | <code d> | ||
+ | mixin a_template!(template_parameters) | ||
+ | </code> | ||
+ | |||
+ | As we will see in the example below, the **mixin** keyword is used in the definitions of template mixins as well. | ||
+ | |||
+ | The instantiation of the template for the specific set of template parameters is inserted into the source code right where the **mixin** keyword appears. | ||
+ | |||
+ | For example, let's have a template that defines both an array of edges and a pair of functions that operate on those edges: | ||
+ | |||
+ | <code d> | ||
+ | mixin template EdgeArrayFeature(T, size_t count) { | ||
+ | T[count] edges; | ||
+ | void setEdge(size_t index, T edge) { | ||
+ | edges[index] = edge; | ||
+ | } | ||
+ | void printEdges() { | ||
+ | writeln("The edges:"); | ||
+ | foreach (i, edge; edges) { | ||
+ | writef("%s:%s ", i, edge); | ||
+ | } | ||
+ | writeln(); | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | That template leaves the type and number of array elements flexible. | ||
+ | For example, the **mixin** below can insert the two-element //**int** array// and the two functions that are generated by the template right inside a **struct** definition: | ||
+ | |||
+ | <code d> | ||
+ | struct Line { | ||
+ | mixin EdgeArrayFeature!(int, 2); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | As a result, **Line** ends up defining a member array and two member functions: | ||
+ | <code d> | ||
+ | import std.stdio; | ||
+ | |||
+ | void main() { | ||
+ | auto line = Line(); | ||
+ | line.setEdge(0, 100); | ||
+ | line.setEdge(1, 200); | ||
+ | line.printEdges(); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | === String mixins === | ||
+ | |||
+ | Another powerful feature of D is being able to insert code as string as long as that string is known at compile time. The syntax of string mixins requires the use of parentheses: | ||
+ | <code d> | ||
+ | mixin (compile_time_generated_string) | ||
+ | </code> | ||
+ | |||
+ | For example, the //hello world// program can be written with a **mixin** as well: | ||
+ | <code d> | ||
+ | import std.stdio; | ||
+ | |||
+ | void main() { | ||
+ | mixin (`writeln("Hello, World!");`); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Obviously, there is no need for mixins in these examples, as the strings could have been written as code as well. | ||
+ | |||
+ | The power of string mixins comes from the fact that the code can be generated at compile time. | ||
+ | |||
+ | A common use case of string mixins is **Operator Overloading**: | ||
+ | <code d> | ||
+ | struct A { | ||
+ | int x; | ||
+ | | ||
+ | ref A opOpAssign(string op)(ref A rhs) | ||
+ | if (op == "+" || op == "-") | ||
+ | { | ||
+ | mixin("this.x" ~ op ~ "= rhs.x;"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void main() { | ||
+ | A a1 = A(20); | ||
+ | A a2 = A(22); | ||
+ | | ||
+ | a1 += a2; | ||
+ | writeln(a1); // -> 42 | ||
+ | a1 -= a2; | ||
+ | writeln(a1); // -> 20 | ||
+ | </code> | ||
+ | |||
+ | Another example is represented by string predicates. Let's consider the following function template that takes an array of numbers | ||
+ | and returns another array that consists of the elements that satisfy a specific condition: | ||
+ | |||
+ | <code d> | ||
+ | int[] filter(string predicate)(int[] numbers) { | ||
+ | int[] result; | ||
+ | foreach (number; numbers) { | ||
+ | if (mixin (predicate)) { | ||
+ | result ~= number; | ||
+ | } | ||
+ | } | ||
+ | return result; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | That function template takes the filtering condition as its template parameter and inserts that condition directly into an if statement as is. | ||
+ | |||
+ | For that condition to choose numbers that are e.g. less than 7, the if condition should look like the following code: | ||
+ | <code d> | ||
+ | if (number < 7) { | ||
+ | </code> | ||
+ | |||
+ | The users of the **filter()** template can provide the condition as a string: | ||
+ | <code d> | ||
+ | int[] numbers = [ 1, 8, 6, -2, 10 ]; | ||
+ | int[] chosen = filter!"number < 7"(numbers); | ||
+ | </code> | ||
+ | |||
+ | Importantly, the name used in the template parameter must match the name of the variable used in the implementation of filter() . So, the template must document what that name should be and the users must use that name. | ||
+ | |||
+ | Phobos uses names consisting of single letters like a, b, n, etc. | ||
+ | |||
+ | <note> | ||
+ | Specifying predicates as strings was used more commonly before the lambda syntax was added to D. Although string predicates as in this example are still used in Phobos, the **=>** lambda syntax may be more suitable in most cases. | ||
+ | </note> | ||
+ | |||
+ | ==== Exercises ==== | ||
+ | |||
+ | === 1. checkedint === | ||
+ | |||
+ | We want to define a **struct CheckedInt** that defines facilities for efficient checking of integral operations against overflow, casting with loss of precision, unexpected change of sign, etc. **CheckedInt** must work with both built-in integral types and other **CheckedInt**s. | ||
+ | |||
+ | CheckedInt offers encapsulated integral wrappers that do all checking internally and have configurable behavior upon erroneous results. For example, **CheckedInt!int** is a type that behaves like int but aborts execution immediately whenever involved in an operation that produces the arithmetically wrong result. | ||
+ | |||
+ | The declaration should look something like | ||
+ | <code d> | ||
+ | struct Checked(T, Hook) | ||
+ | </code> | ||
+ | |||
+ | In order to work with built-in types, you need to define: opAssign, opBinary, opBinaryRight, opCast, opCmp, opEquals, opOpAssign, opUnary. | ||
+ | |||
+ | CheckedInt has customizable behavior with the help of a second type parameter, Hook. Depending on the Hook type, core operations on the underlying integral may be verified for overflow or completely redefined. Implement the following predefined hooks: | ||
+ | - **Abort** : fails every incorrect operation with a message to std.stdio. stderr followed by a call to assert(0). It is the default second parameter, i.e. Checked!short is the same as Checked!(short, Abort). | ||
+ | - **Throw** : fails every incorrect operation by throwing an exception. | ||
+ | - **Warn**: prints incorrect operations to std.stdio.stderr but otherwise preserves the built-in behavior. | ||
+ | |||
+ | |||
+ | |||
+ |