質問
D 2.0で遊んでいると、次の問題が見つかりました。
例1:
pure string[] run1()
{
string[] msg;
msg ~= "Test";
msg ~= "this.";
return msg;
}
これはコンパイルされ、期待どおりに動作します。
クラスで文字列配列をラップしようとすると、これを機能させることができません:
class TestPure
{
string[] msg;
void addMsg( string s )
{
msg ~= s;
}
};
pure TestPure run2()
{
TestPure t = new TestPure();
t.addMsg("Test");
t.addMsg("this.");
return t;
}
addMsg関数が不純であるため、このコードはコンパイルされません。 TestPureオブジェクトを変更するため、この関数を純粋にすることはできません。 何か不足していますか?それとも制限ですか?
以下はコンパイルします:
pure TestPure run3()
{
TestPure t = new TestPure();
t.msg ~= "Test";
t.msg ~= "this.";
return t;
}
〜=演算子は、msg配列の不純な関数として実装されていませんか? run1関数でコンパイラが文句を言わないのはなぜですか?
解決
v2.050以降、Dは pure
の定義を緩和して、いわゆる "weakly pure"と呼ばれるものを受け入れました。機能します。これは、" グローバルな可変状態を読み書きしない"。弱純関数は、関数言語の意味で純関数とは同じではありません。唯一の関係は、それらが本当の純粋な関数を作るということです。 OPの例のように、関数は弱い関数を呼び出すことができます。
これにより、 addMsg
は(弱い) pure
としてマークできます。これは、ローカル変数 this.msg
が変更されます:
class TestPure
{
string[] msg;
pure void addMsg( string s )
{
msg ~= s;
}
};
そしてもちろん、修正なしで(強力に) pure
関数 run2
を使用できるようになりました。
pure TestPure run2()
{
TestPure t = new TestPure();
t.addMsg("Test");
t.addMsg("this.");
return t;
}
他のヒント
addMsgは純粋ではなく、オブジェクトの状態を変更するため純粋ではないことを既に指摘しています。
純粋にする唯一の方法は、行っている変更をカプセル化することです。これを行う最も簡単な方法は、復帰突然変異によるものです。これを実装する方法は2つあります。
まず、次のようにします:
class TestPure
{
string[] msg;
pure TestPure addMsg(string s)
{
auto r = new TestPure;
r.msg = this.msg.dup;
r.msg ~= s;
return r;
}
}
純粋な関数内では、this参照は実際にはconstであるため、前の配列をコピーする必要があります。最終サイズの新しい配列を割り当ててから、自分で要素をコピーすることにより、コピーをより適切に実行できることに注意してください。この関数は次のように使用します。
pure TestPure run3()
{
auto t = new TestPure;
t = t.addMsg("Test");
t = t.addMsg("this.");
return t;
}
この方法では、変更は戻り値を介して渡される変更を伴う各純関数に限定されます。
TestPureを記述する別の方法は、コンストラクターに渡す前にメンバーをconstにし、すべての変更を行うことです。
class TestPure
{
const(string[]) msg;
this()
{
msg = null;
}
this(const(string[]) msg)
{
this.msg = msg;
}
pure TestPure addMsg(string s)
{
return new TestPure(this.msg ~ s);
}
}
役立つこと。
純関数の定義を確認してください:
- http://en.wikipedia.org/wiki/Pure_function
- http://www.digitalmars.com/d/2.0 /function.html#pure-functions
純粋な関数は、同じ引数に対して同じ結果を生成する関数です。そのために、純粋な関数:
- パラメータはすべて不変であるか、暗黙的に不変に変換可能です
- グローバルな可変状態の読み取りまたは書き込みを行いません
純粋な関数を使用することの効果の1つは、安全に並列化できることです。ただし、関数の複数のインスタンスを同時に実行することは安全ではありません。これらは両方とも同時にクラスインスタンスを変更し、同期の問題を引き起こす可能性があるためです。
私はあなたのコードが概念的に正しいと考えます。ただし、コンパイラのセマンティック分析が脳のセマンティクスほど良くない場合があります。
クラスのソースが利用できない場合を検討してください。その場合、コンパイラは addMsg
がメンバ変数を変更するだけであるため、純粋な関数から呼び出すことを許可できないことを通知する方法がありません。
あなたのケースでそれを許可するには、このタイプの使用法のための特別なケース処理が必要です。追加されたすべての特殊なケースルールは、言語をより複雑にします(または、ドキュメント化されていない場合は、移植性が低下します)
ごくわずかですが、この関数は常に同じ結果を返すとは限りません。
参照してください。あるオブジェクトへの参照を返します。オブジェクトには常に同じデータが含まれますが、同じ関数の複数の呼び出しで返されるオブジェクトは同一ではありません。つまり、それらは同じメモリアドレスを持っていません。
オブジェクトへの参照を返すとき、本質的にメモリアドレスを返します。これは、複数の呼び出しで異なることになります。
別の考え方として、戻り値の一部はオブジェクトのメモリアドレスであり、これは一部のグローバル状態に依存します。関数の出力がグローバル状態に依存する場合、純粋ではありません。地獄、それに依存する必要さえありません。関数がグローバル状態を読み取る限り、純粋ではありません。 " new"を呼び出すことで、グローバル状態を読み取ります。