静的メソッドとインスタンスメソッドについての議論では、Sqrt()
は静的メソッドではなく数値型のインスタンスメソッドである必要があるといつも思っています。何故ですか?それは明らかに値に作用します。
// looks wrong to me
var y = Math.Sqrt(x);
// looks better to me
var y = x.Sqrt();
値の型は明らかにインスタンスメソッドを持つことができます。多くの言語では、インスタンスメソッドToString()
があります。
コメントからいくつかの質問に答えるには:1.Sqrt()
はなぜ合法ではないのですか? 1.ToString()
です。
一部の言語では値型のメソッドを許可していませんが、一部の言語では許可しています。 Java、ECMAScript、C#、Python(with __str__(self)
defined)など)について話しています。同じことがceil()
、floor()
など.
それは完全に言語設計の選択です。また、プリミティブ型の基本的な実装、およびそのためのパフォーマンスの考慮事項にも依存します。
.NETには 1つの静的Math.Sqrt
メソッドはdouble
に作用し、double
を返します。他に渡すものはすべてdouble
にキャストまたは昇格する必要があります。
double sqrt2 = Math.Sqrt(2d);
一方、Rust which これらの演算を型の関数として公開する :
let sqrt2 = 2.0f32.sqrt();
let higher = 2.0f32.max(3.0f32);
ただし、Rustにはユニバーサル関数呼び出し構文(以前に誰かが言及した人)もいる)なので、好きなものを選択できます。
let sqrt2 = f32::sqrt(2.0f32);
let higher = f32::max(2.0f32, 3.0f32);
新しい言語を設計していて、Sqrt
をインスタンスメソッドにしたいとします。 double
クラスを見て、設計を開始します。 (インスタンス以外の)入力がないことは明らかで、double
を返します。コードを記述してテストします。完璧。
しかし、整数の平方根を取ることも有効であり、平方根を取るためだけに全員にdoubleへの変換を強制したくありません。 int
に移動して、設計を開始します。それは何を返しますか? couldはint
を返し、完全な正方形でのみ機能するようにするか、結果を最も近いint
に丸めます(適切な丸め方法に関する議論は今のところ無視します) )。しかし、誰かが整数以外の結果を求めている場合はどうなりますか? 2つのメソッドがある場合、1つはint
を返し、もう1つはdouble
を返します(一部の言語では名前を変更しないと不可能です)。したがって、double
を返す必要があると判断しました。今、実装します。ただし、実装はdouble
に使用したものと同じです。コピーして貼り付けますか?インスタンスをdouble
にキャストしてthatインスタンスメソッドを呼び出しますか?両方のクラスからアクセスできるライブラリメソッドにロジックを配置しないでください。ライブラリMath
および関数Math.Sqrt
を呼び出します。
Math.Sqrt
が静的関数である理由:
他の議論についても触れていません。
GetSqrt
という名前にする必要がありますか?Square
はどうですか? Abs
? Trunc
? Log10
? Ln
? Power
? Factorial
? Sin
? Cos
? ArcTan
?数学演算は、多くの場合、パフォーマンスに非常に影響されます。したがって、コンパイル時に完全に解決(および最適化、またはインライン化)できる静的メソッドを使用します。一部の言語では、静的にディスパッチされるメソッドを指定するメカニズムが提供されていません。さらに、多くの言語のオブジェクトモデルにはかなりのメモリオーバーヘッドがあり、double
などの「プリミティブ」タイプには受け入れられません。
一部の言語では、メソッド呼び出し構文を使用する関数を定義できますが、実際には静的にディスパッチされます。 C#3.0以降の拡張メソッドは一例です。非仮想メソッド(C++のメソッドのデフォルトなど)も別のケースですが、C++はプリミティブ型のメソッドをサポートしていません。もちろん、ランタイムのオーバーヘッドなしに、さまざまなメソッドでプリミティブ型を装飾する独自のラッパークラスをC++で作成することもできます。ただし、値を手動でそのラッパータイプに変換する必要があります。
数値型にメソッドを定義する言語がいくつかあります。これらは通常、すべてがオブジェクトである非常に動的な言語です。ここでは、パフォーマンスは概念の優雅さに対する二次的な考慮事項ですが、これらの言語は一般的に数値の計算には使用されません。ただし、これらの言語には、プリミティブの操作を「ボックス化解除」できるオプティマイザがある場合があります。
技術的な考慮事項がわかれば、そのようなメソッドベースの数学インターフェースが優れたインターフェースになるかどうかを検討できます。 2つの問題が発生します。
42.sqrt
_などの式は、多くのユーザーにとってsqrt(42)
よりもはるかに異質に見えます。数学を多用するユーザーとして、私はドットメソッド呼び出し構文よりも独自の演算子を作成する機能を好みます。mean
、median
、variance
、std
、normalize
数値リスト、または数値のガンマ関数)が役立ちます。汎用言語の場合、これはインターフェースを圧迫するだけです。重要でない操作を別の名前空間に委任すると、大部分のユーザーがその型にアクセスしやすくなります。特別な目的の数学関数がたくさんあり、ユーティリティクラスにそれらの関数のすべて(またはランダムなサブセット)をすべての数学型に入力するのではなく、それらの関数に動機付けられます。それ以外の場合は、オートコンプリートのツールチップを汚染するか、常に2か所を見るように強制します。 (sin
はDouble
のメンバーになるのに十分重要ですか、それともMath
クラスのhtan
やexp1p
などの近交系と一緒ですか? ?)
もう1つの実際的な理由は、数値メソッドを実装するにはさまざまな方法があり、パフォーマンスと精度のトレードオフが異なることが判明しているためです。 JavaにはMath
があり、StrictMath
もあります。
ここで奇妙な対称性が作用していることを正しく観察しました。
私がsqrt(n)
とn.sqrt()
のどちらを言っても、どちらも重要ではありません。どちらも同じことを表し、どちらが好きかは何よりも個人的な好みの問題です。
そのため、2つの構文を交換可能にするという特定の言語設計者からの強い議論があります。 Dプログラミング言語では、これを niform Function Call Syntax という機能で既に許可しています。同様の機能も C++での標準化が提案されています です。 Mark Ameryがコメントで指摘しているように 、Pythonもこれを許可します。
これは問題がないわけではありません。このような基本的な構文の変更を導入すると、既存のコードにさまざまな影響が及ぶだけでなく、2つの構文を異なるものとして説明するように何十年も訓練されてきた開発者の間で議論の余地のある議論のトピックでもあります。
長期的には2つの統合が実現可能かどうかを判断できるのは時間だけだと思いますが、これは間違いなく興味深い考慮事項です。
D Stanley の回答に加えて、ポリモーフィズムについて考える必要があります。 Math.Sqrtのようなメソッドは、常に同じ値を同じ入力に返す必要があります。静的メソッドはオーバーライドできないため、メソッドを静的にすることは、この点を明確にする良い方法です。
ToString()メソッドについて言及しました。ここでは、このメソッドをオーバーライドすることができます。そのため、(サブ)クラスは、親クラスとしてのStringとして別の方法で表されます。したがって、それをインスタンスメソッドにします。
さて、Javaには、すべての基本型のラッパーがあります。
そして基本型はクラス型ではなく、メンバー関数もありません。
したがって、次の選択肢があります。
Math
のようなプロ形式クラスに収集します。... JavaはJavaであり、支持者はそのようにそれを好きであると公言します。
ここで、オプション3を除外することもできます。オブジェクトの割り当てはかなり安価ですが、それは無料ではなく、何度も繰り返しますdoesaddアップ。
2つ下がって、1つはまだ殺す:オプション2も悪い考えです。なぜなら、every関数がeveryタイプ、ギャップを埋めるために拡大変換に依存することはできません。そうしないと、不整合が本当に害になります。
そして Java.lang.Math
、特にint
それぞれのdouble
より小さいタイプの場合、ギャップのロットがあります。
したがって、最終的には、明確な勝利者はオプション1であり、それらすべてをユーティリティ関数クラスの1つの場所に収集します。
オプション4に戻ると、その方向の何かが実際にずっと後で発生しました。かなり長い間名前を解決するときに、必要なクラスのすべての静的メンバーを検討するようコンパイラーに要求できます。 import static someclass.*;
余談ですが、他の言語には無料の関数(オプションで名前空間を使用)に対する偏見がないか、小さい型がはるかに少ないため、他の言語にはその問題はありません。
(amonがほのめかしてはいますが)明示的に言及していない1つの点は、平方根は「派生」演算と見なすことができるということです。実装がそれを提供しない場合は、独自に作成できます。
質問には言語デザインのタグが付けられているため、言語に依存しない説明を検討する場合があります。多くの言語には異なる哲学がありますが、パラダイム全体で、カプセル化を使用して不変条件を保持することは非常に一般的です。つまり、その型が示唆するように動作しない値を避けるためです。
たとえば、マシンワードを使用した整数の実装がある場合、おそらく何らかの方法で表現をカプセル化する必要があります(たとえば、ビットシフトによる符号の変更を防ぐため)が、同時にこれらのビットにアクセスして、次のような操作を実装する必要があります。添加。
一部の言語では、クラスとプライベートメソッドを使用してこれを実装できます。
class Int {
public Int add(Int x) {
// Do something with the bits
}
private List<Boolean> getBits() {
// ...
}
}
一部のモジュールシステム:
signature INT = sig
type int
val add : int -> int -> int
end
structure Word : INT = struct
datatype int = (* ... *)
fun add x y = (* Do something with the bits *)
fun getBits x = (* ... *)
end
字句スコープを持つもの:
(defun getAdder ()
(let ((getBits (lambda (x) ; ...
(add (lambda (x y) ; Do something with the bits
'add))
等々。ただし、平方根の実装にはこれらのメカニズムのnoneが必要です。数値型のpublicインターフェースを使用して実装できるため、カプセル化された実装の詳細。
したがって、平方根の場所は、言語とライブラリデザイナーの哲学/味に帰着します。数値の「内部」に置くことを選択する人もいます(たとえば、インスタンスメソッドにする)、プリミティブ演算と同じレベルに置くことを選ぶ人もいます(これはインスタンスメソッドを意味する場合もあれば、生きていることを意味する場合もあります- outside数値、ただしinside同じモジュール/クラス/名前空間、たとえばスタンドアロン関数または静的メソッド)、「ヘルパー」のコレクションに配置することを選択する人もいます関数、サードパーティのライブラリに委任することを選択する場合があります。