Difference between revisions of "Mixin Macros Pattern"

From D Wiki
Jump to: navigation, search
(Added solution)
m
 
(5 intermediate revisions by one other user not shown)
Line 3: Line 3:
 
== Problem: Repetitive code with slight variations ==
 
== Problem: Repetitive code with slight variations ==
 
Example: Arithmetic operator overloading
 
Example: Arithmetic operator overloading
 +
 
Want:
 
Want:
 
* Minimal code duplication
 
* Minimal code duplication
Line 10: Line 11:
 
== Solution: Mixins + Compile-Time Magic ==
 
== Solution: Mixins + Compile-Time Magic ==
 
<syntaxhighlight lang="d">
 
<syntaxhighlight lang="d">
// D operator overloading was designed around mixins
+
// D's operator overloading was designed around mixins
 
struct Int  
 
struct Int  
 
{
 
{
 
     Int opBinary(string op)(Int rhs)
 
     Int opBinary(string op)(Int rhs)
 
     {
 
     {
       int value = mixin("value_ " ~ op ~ "rhs.value_");
+
       int value = mixin("value_ " ~ op ~ " rhs.value_");
 
       return Int(value);
 
       return Int(value);
 
     }
 
     }
Line 33: Line 34:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
The way D's operating overloading works, is there's only one function to overload for all binary operators, as opposed to the C++ where each operator is it's own individual implementation.  The function takes a string, that represents its operator, as a template parameter.  For example, to implement the addition operator, the <code>opBinary</code> template will be instantiated with the "+" string, and to implement the subtraction operator, it will instantiated with the "-" string. The beauty of this is it enables reduction of boilerplate code with one mixin macro handling all binary operators.
 +
 +
In the example above, the lhs value (<code>value_</code>), the operator string (<code>op</code>), and the rhs value (<code>rhs.value_</code>) are appended to one another and then mixed in. When mixed in, the resulting string is evaluated at the current scope as regular code.  In the example above it would be prudent to add a template constraint to verify that the operator is either a "+" or a "-" since those are the only implementations.  It doesn't have the performance cost of runtime evaluation, and unless the build system is compromised there's no security risk since it's all evaluated at compile-time.  It's using compile-time computations to essentially create macros.
 +
 +
[[Category:DesignPattern]]

Latest revision as of 10:19, 14 November 2014

From David Simcha's D-Specific Design Patterns talk at DConf 2013.

Problem: Repetitive code with slight variations

Example: Arithmetic operator overloading

Want:

  • Minimal code duplication
  • Ability to use templates/CTFE
  • (Avoid (Lispy (syntax)))

Solution: Mixins + Compile-Time Magic

// D's operator overloading was designed around mixins
struct Int 
{
    Int opBinary(string op)(Int rhs)
    {
       int value = mixin("value_ " ~ op ~ " rhs.value_");
       return Int(value);
    }

    int value() @property 
    { 
        return value_; 
    }

    private int value_;
}

void main()
{
    assert((Int(2) - Int(5)).value == -3);
    assert((Int(2) + Int(4)).value == 7);
}

The way D's operating overloading works, is there's only one function to overload for all binary operators, as opposed to the C++ where each operator is it's own individual implementation. The function takes a string, that represents its operator, as a template parameter. For example, to implement the addition operator, the opBinary template will be instantiated with the "+" string, and to implement the subtraction operator, it will instantiated with the "-" string. The beauty of this is it enables reduction of boilerplate code with one mixin macro handling all binary operators.

In the example above, the lhs value (value_), the operator string (op), and the rhs value (rhs.value_) are appended to one another and then mixed in. When mixed in, the resulting string is evaluated at the current scope as regular code. In the example above it would be prudent to add a template constraint to verify that the operator is either a "+" or a "-" since those are the only implementations. It doesn't have the performance cost of runtime evaluation, and unless the build system is compromised there's no security risk since it's all evaluated at compile-time. It's using compile-time computations to essentially create macros.