1

代数システムを構築したいので、基本的に何らかのデータ型であるキャリアと、その型に対する一連の演算子が必要です。代数のシグネチャが異なるのは自然なことであり、同じ型が同じ表記法で異なる演算子のセットを持つ可能性があることを意味します。

私にはvectorタイプがあるとしましょう。通常、ユークリッド メトリックとノルムを使用するため、ベクトル タイプのデータ宣言を含む をインポートvector, euclideanvectorますが、同じベクトルのオーバーロードされたすべての演算子は に移動しeuclideanます。次に、リーマニア空間で作業したい場合はvector, riemanian、同じインターフェイスでまったく異なる代数をインポートして取得するだけです。

これは継承を介してオブジェクトパラダイムで達成できることは知っていますが、プレーンモジュールでそれを行うことは可能でしょうか? 必要なのは、同じ構造に対して、あるモジュールでデータを宣言し、他のモジュールで演算子を宣言することだけです。

4

1 に答える 1

2

2 つの可能性が思い浮かびます。1 つは 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));

別の可能な方法は、データを構造体または mixin テンプレートに配置し、必要な関数を使用して別の構造体に配置して計算を行うことです。

// 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
}

これは myvectormath.vector で、これは myvectormath2 であるため、変数 "a" を渡すと問題が発生します。

test32.d(27): エラー: 関数 test32.somethingWithMath2 (ベクトル a) は、引数の型 (ベクトル) を使用して呼び出すことはできません

しかし、外部データ構造体、ctor、および mixin テンプレートのエイリアス this のおかげで、それらを非常に簡単に変換できます。

    somethingWithMath2(myvectormath2.vector(a));

コンパイル!内部で動作する方法は、myvectormath2.vector に (float、float) と (VectorData) の 2 つのコンストラクターがあることです。どちらも 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 を追加できないことに注意してください。これはタイプの不一致になります。ただし、それを許可たい場合は、オーバーロードされた演算子を変更して、数学タイプの 1 つではなく VectorData を受け入れるようにするだけです! 次に、暗黙的に変換され、作業する同じデータが得られます。VectorData は、OOP 用語の基本クラスであると考えてください。

これで基本はカバーされていると思います。さらに質問がある場合はお知らせください。

于 2013-10-09T14:59:44.637 に答える