1

我想建立代数系统,所以我需要一个载体,它基本上是某种数据类型,以及该类型的一堆运算符。代数的签名不同是很自然的,这意味着同一类型可能具有具有相同符号的不同运算符集。

说我有一个vector类型。通常我会使用欧几里得度量和范数,所以我 import vector, euclidean,其中vector包含向量类型的数据声明,但同一向量的所有重载运算符都转到euclidean. 然后,当我想使用黎曼空间时,我只需导入vector, riemanian并获得具有相同界面的完全不同的代数。

我知道,这可以通过继承在对象范式中实现,但也许可以用普通模块来做到这一点?我所需要的只是在一个模块中声明数据,在另一个模块中声明运算符,以实现相同的结构。

4

1 回答 1

2

我想到了两种可能性。一个是使用 UFCS,在将类型作为第一个参数的其他模块中定义命名函数(它不适用于运算符重载),然后可以使用点语法调用(如果我在这里搞砸了数学,请原谅我):

module myvector;
struct vector {
     float x;
 float y;
}

module myvectormath;
import myvector;
vector add(vector lhs, vector rhs) {
     // inside, it is just a regular function
     vector result;
 result.x = lhs.x + rhs.x;
 result.y = lhs.y + rhs.y;
 return result;
}

用法:

import myvector;
import myvectormath;

// but it can be called with dot notation
vector a = vector(0,0).add(vector(5, 5));

另一种可能的方法是将数据放入结构或混合模板中,然后通过将其放入具有所需功能的另一个结构中来进行数学运算:

// data definition
module myvector;

// the data will be an external named type, so we can pass it on more easily - will help interop
struct VectorData {
   float x;
   float y;
}

// and this provides the stuff to get our other types started
mixin template vector_payload() {
// constructors for easy initialization
this(float x, float y) {
        _data.x = x;
    _data.y = y;
}
this(VectorData d) {
        _data = d;
}

    // storing our data
VectorData _data;

// alias this is a feature that provides a bit of controlled implicit casting..
alias _data this;
}

// math module #1
module myvectormath;
import myvector;

struct vector {
    // mixin all the stuff from above, so we get those ctors, the data, etc.
    mixin vector_payload!();

// and add our methods, including full operator overloading
    vector opBinary(string op:"+")(vector rhs) {
        vector result;
        result.x = this.x + rhs.x;
        result.y = this.y + rhs.y;
        return result;
    }
}

// math module #2
module myvectormath2;
import myvector;

struct vector {
    // again, mix it in
    mixin vector_payload!();

// and add our methods
    vector opBinary(string op:"+")(vector rhs) {
        vector result;
    // this one has horribly broken math lol
        result.x = this.x - rhs.x;
        result.y = this.y - rhs.y;
        return result;
    }
}

// usage
import myvectormath;
// OR
//import myvectormath2;
void main() {
    vector a = vector(0, 0) + vector(5, 5);
    import std.stdio;
    writeln(a);
}

在使用模块中,如果您只是替换导入,则其余代码保持不变。但是,如果您想同时使用这两个模块并将它们混合使用会发生什么?这就是内部结构 _Data、构造函数和别名这个魔法的用武之地。首先,我们将两者都导入,看看会发生什么:

test32.d(23):错误:test324.d(4) 处的 myvectormath.vector 与 test322.d(4) 处的 myvectormath2.vector 冲突

因此,首先,我们要消除名称的歧义。有各种方法可以做到这一点,您可以在 D 文档的导入部分了解更多信息:http: //dlang.org/module.html#Import

现在,我将只使用完全限定名称。

// usage
import myvectormath;
import myvectormath2;
void main() {
    // specify the kind we want to use here...
    myvectormath.vector a = myvectormath.vector(0, 0) + myvectormath.vector(5, 5);
    import std.stdio;
    writeln(a); // and we get a result of 0, 5, so it used the addition version correctly
}

我们怎样才能轻松地在内部移动它们?让我们创建一个使用版本 #2 的函数:

void somethingWithMath2(myvectormath2.vector vec) {
// whatever
}

如果您将变量“a”传递给它,它会抱怨,因为它是 myvectormath.vector,这是 myvectormath2。

test32.d(27): 错误: 函数 test32.somethingWithMath2 (vector a) is not callable using argument types (vector)

但是,我们可以很容易地转换它们,这要归功于 mixin 模板中的外部数据结构、ctor 和别名:

    somethingWithMath2(myvectormath2.vector(a));

编译!在幕后工作的方式是 myvectormath2.vector 有两个构造函数:(float, float) 和 (VectorData)。它们都不匹配 a 的类型,所以接下来它会尝试 a 的别名 this... 即 VectorData。所以它隐式转换然后匹配 VectorData ctor。

您也可以只传递数据:

import myvector;
void somethingWithMath2(VectorData a_in) {
// to do math on it, we construct the kind of vectormath we're interested in:
auto a = myvectormath2.vector(a_in);
// and use it
}

然后这样称呼它:

// will implicitly convert any of the sub vectormath types to the base data so this just works
somethingWithMath2(a);

传递数据可能是最好的,因为那时调用者不需要知道你会用它做什么。

顺便说一句,它在这里使用的构造函数是微不足道的,并且不应该导致显着的运行时损失(如果编译器开关设置为内联它,则可能根本没有;这基本上只是一个 reinterpret_cast;数据表示是相同的)。

请注意,它不允许您添加 myvectormath2.vector + myvectormath.vector,这将是类型不匹配。但是,如果您确实想允许这样做,您所要做的就是将重载的运算符更改为接受 VectorData 而不是其中一种数学类型!然后它将隐式转换,并且您可以处理相同的数据。将 VectorData 视为 OOP 术语中的基类。

我认为这涵盖了基础,如果您还有其他问题,请告诉我。

于 2013-10-09T14:59:44.637 回答