Operator/concept interoperability

TheFlyingFiddle via Digitalmars-d digitalmars-d at puremagic.com
Tue Jun 3 15:05:26 PDT 2014


On Tuesday, 3 June 2014 at 19:55:39 UTC, Mason McGill wrote:
> I have a numerical/multimedia library that defines the concept 
> of an n-dimensional function sampled on a grid, and operations 
> on such grids. `InputGrid`s (analogous to `InputRange`s) can be 
> dense or sparse multidimensional arrays, as well the results of 
> lazy operations on other grids and/or functions 
> (map/reduce/zip/broadcast/repeat/sample/etc.).
>
> UFCS has been extremely beneficial to my API, enabling things 
> like this:
>
>   DenseGrid!(float, 2) x = zeros(5, 5);
>   auto y = x.map!exp.reduce!max;
>
> without actually defining `map` inside `DenseGrid` or `reduce` 
> inside `MapResult`. `map` and `reduce` are defined once, at 
> module scope, and work with any `InputGrid`.
>
> As this is numerical code, it would be great to be able to do 
> this with operators, as is possible in C++, Julia, and F#:
>
>   auto opUnary(string op, Grid)(Grid g) if (isInputGrid!Grid)
>     { /* Enable unary operations for *any* `InputGrid`. */ }
>
>   DenseGrid!(float, 2) x = zeros(5, 5);
>   auto y = -x;
>
> This is currently not supported, which means users of my 
> library get functions like `map` and `reduce` that work "out of 
> the box" for any grids they define, but they need to do extra 
> work to use "convenient" operator syntax for NumPy-style 
> elementwise operations.
>
> Based on my limited knowledge of DMD internals, I take it this 
> behavior is the result of an intentional design decision rather 
> than a forced technical one. Can anyone explain the reasoning 
> behind it?
>
> Also, does anyone else have an opinion for/against allowing the 
> definition of operators that operate on concepts?
>
> Thanks for your time,
> -MM

> Based on my limited knowledge of DMD internals, I take it this 
> behavior is the result of an intentional design decision rather 
> than a forced technical one. Can anyone explain the reasoning 
> behind it?

Well one reason for this is that unlike methods it is hard to
resolve ambiguity between diffrent operator overloads that have
been defined in diffrent modules.

Example: 2D-vectors
//vector.d
struct Vector
{
     float x, y;
}

//cartesian.d
Vector opBinary(string op : "+")(ref Vector lhs, ref Vector rhs)
{
    //Code for adding two cartesian vectors.
}

//polar.d
Vector opBinary(string op : "+")(ref Vector lhs, ref Vector rhs)
{
     //Code for adding two polar vectors.
}

//main.d
import polar, vector;
void main()
{
     auto a = Vector(2, 5);
     auto b = Vector(4, 10);

     auto c = a + b; //What overload should we pick here?

     //This ambiguity could potentially be resolved like this:
     auto d = polar.opBinary!"+"(a, b);
     //But... This defeats the whole purpose of operators.
}


Side note:

You can achieve what you want to do with template mixins.

Example:

//Something more meaningful here.
enum isInputGrid(T) = true;

mixin template InputGridOperators()
{

    static if(isInputGrid!(typeof(this)))
    auto opUnary(string s)()
    {
       //Unary implementation
    }

    static if(isInputGrid!(typeof(this)))
    auto opBinary(string s, T)(ref T rhs) if(isInputGrid!(T))
    {

    }

    //etc.
}

struct DenseGrid(T, size_t N)
{
     mixin InputGridOperators!();
     //Implemtation of dense grid
}

While this implementation is not as clean as global operator
overloading it works today and it makes it very simple to add
operators to new types of grids.






















More information about the Digitalmars-d mailing list