web-dev-qa-db-ja.com

Math.Sqrt()が静的関数なのはなぜですか?

静的メソッドとインスタンスメソッドについての議論では、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()など.

31
Residuum

それは完全に言語設計の選択です。また、プリミティブ型の基本的な実装、およびそのためのパフォーマンスの考慮事項にも依存します。

.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);
20
angelsl

新しい言語を設計していて、Sqrtをインスタンスメソッドにしたいとします。 doubleクラスを見て、設計を開始します。 (インスタンス以外の)入力がないことは明らかで、doubleを返します。コードを記述してテストします。完璧。

しかし、整数の平方根を取ることも有効であり、平方根を取るためだけに全員にdoubleへの変換を強制したくありません。 intに移動して、設計を開始します。それは何を返しますか? couldintを返し、完全な正方形でのみ機能するようにするか、結果を最も近いintに丸めます(適切な丸め方法に関する議論は今のところ無視します) )。しかし、誰かが整数以外の結果を求めている場合はどうなりますか? 2つのメソッドがある場合、1つはintを返し、もう1つはdoubleを返します(一部の言語では名前を変更しないと不可能です)。したがって、doubleを返す必要があると判断しました。今、実装します。ただし、実装はdoubleに使用したものと同じです。コピーして貼り付けますか?インスタンスをdoubleにキャストしてthatインスタンスメソッドを呼び出しますか?両方のクラスからアクセスできるライブラリメソッドにロジックを配置しないでください。ライブラリMathおよび関数Math.Sqrtを呼び出します。

Math.Sqrtが静的関数である理由:

  • 基礎となる数値型に関係なく実装は同じであるため
  • 特定のインスタンスには影響しないため(1つの値を取り、結果を返す)
  • 数値型はその機能にdependを行わないため、別のクラスに含めることは理にかなっています

他の議論についても触れていません。

  • returnsインスタンスを変更するのではなく新しい値なので、GetSqrtという名前にする必要がありますか?
  • Squareはどうですか? AbsTruncLog10LnPowerFactorialSinCosArcTan
65
D Stanley

数学演算は、多くの場合、パフォーマンスに非常に影響されます。したがって、コンパイル時に完全に解決(および最適化、またはインライン化)できる静的メソッドを使用します。一部の言語では、静的にディスパッチされるメソッドを指定するメカニズムが提供されていません。さらに、多くの言語のオブジェクトモデルにはかなりのメモリオーバーヘッドがあり、doubleなどの「プリミティブ」タイプには受け入れられません。

一部の言語では、メソッド呼び出し構文を使用する関数を定義できますが、実際には静的にディスパッチされます。 C#3.0以降の拡張メソッドは一例です。非仮想メソッド(C++のメソッドのデフォルトなど)も別のケースですが、C++はプリミティブ型のメソッドをサポートしていません。もちろん、ランタイムのオーバーヘッドなしに、さまざまなメソッドでプリミティブ型を装飾する独自のラッパークラスをC++で作成することもできます。ただし、値を手動でそのラッパータイプに変換する必要があります。

数値型にメソッドを定義する言語がいくつかあります。これらは通常、すべてがオブジェクトである非常に動的な言語です。ここでは、パフォーマンスは概念の優雅さに対する二次的な考慮事項ですが、これらの言語は一般的に数値の計算には使用されません。ただし、これらの言語には、プリミティブの操作を「ボックス化解除」できるオプティマイザがある場合があります。


技術的な考慮事項がわかれば、そのようなメソッドベースの数学インターフェースが優れたインターフェースになるかどうかを検討できます。 2つの問題が発生します。

  • 数学的表記は、メソッドではなく、演算子と関数に基づいています。 _42.sqrt_などの式は、多くのユーザーにとってsqrt(42)よりもはるかに異質に見えます。数学を多用するユーザーとして、私はドットメソッド呼び出し構文よりも独自の演算子を作成する機能を好みます。
  • 単一責任原則では、タイプの一部である操作の数を必須の操作に制限することを推奨しています。乗算と比較して、平方根が必要になることはめったにありません。言語が統計的分析を特に目的としている場合は、より多くのプリミティブ(操作meanmedianvariancestdnormalize数値リスト、または数値のガンマ関数)が役立ちます。汎用言語の場合、これはインターフェースを圧迫するだけです。重要でない操作を別の名前空間に委任すると、大部分のユーザーがその型にアクセスしやすくなります。
25
amon

特別な目的の数学関数がたくさんあり、ユーティリティクラスにそれらの関数のすべて(またはランダムなサブセット)をすべての数学型に入力するのではなく、それらの関数に動機付けられます。それ以外の場合は、オートコンプリートのツールチップを汚染するか、常に2か所を見るように強制します。 (sinDoubleのメンバーになるのに十分重要ですか、それともMathクラスのhtanexp1pなどの近交系と一緒ですか? ?)

もう1つの実際的な理由は、数値メソッドを実装するにはさまざまな方法があり、パフォーマンスと精度のトレードオフが異なることが判明しているためです。 JavaにはMathがあり、StrictMathもあります。

15

ここで奇妙な対称性が作用していることを正しく観察しました。

私がsqrt(n)n.sqrt()のどちらを言っても、どちらも重要ではありません。どちらも同じことを表し、どちらが好きかは何よりも個人的な好みの問題です。

そのため、2つの構文を交換可能にするという特定の言語設計者からの強い議論があります。 Dプログラミング言語では、これを niform Function Call Syntax という機能で既に許可しています。同様の機能も C++での標準化が提案されています です。 Mark Ameryがコメントで指摘しているように 、Pythonもこれを許可します。

これは問題がないわけではありません。このような基本的な構文の変更を導入すると、既存のコードにさまざまな影響が及ぶだけでなく、2つの構文を異なるものとして説明するように何十年も訓練されてきた開発者の間で議論の余地のある議論のトピックでもあります。

長期的には2つの統合が実現可能かどうかを判断できるのは時間だけだと思いますが、これは間違いなく興味深い考慮事項です。

6
ComicSansMS

D Stanley の回答に加えて、ポリモーフィズムについて考える必要があります。 Math.Sqrtのようなメソッドは、常に同じ値を同じ入力に返す必要があります。静的メソッドはオーバーライドできないため、メソッドを静的にすることは、この点を明確にする良い方法です。

ToString()メソッドについて言及しました。ここでは、このメソッドをオーバーライドすることができます。そのため、(サブ)クラスは、親クラスとしてのStringとして別の方法で表されます。したがって、それをインスタンスメソッドにします。

3
falx

さて、Javaには、すべての基本型のラッパーがあります。
そして基本型はクラス型ではなく、メンバー関数もありません。

したがって、次の選択肢があります。

  1. これらすべてのヘルパー関数をMathのようなプロ形式クラスに収集します。
  2. 対応するラッパーの静的関数にします。
  3. 対応するラッパーのメンバー関数にします。
  4. Javaのルールを変更します。

... JavaはJavaであり、支持者はそのようにそれを好きであると公言します。

ここで、オプション3を除外することもできます。オブジェクトの割り当てはかなり安価ですが、それは無料ではなく、何度も繰り返しますdoesaddアップ。

2つ下がって、1つはまだ殺す:オプション2も悪い考えです。なぜなら、every関数がeveryタイプ、ギャップを埋めるために拡大変換に依存することはできません。そうしないと、不整合が本当に害になります。
そして Java.lang.Math 、特にintそれぞれのdoubleより小さいタイプの場合、ギャップのロットがあります。

したがって、最終的には、明確な勝利者はオプション1であり、それらすべてをユーティリティ関数クラスの1つの場所に収集します。

オプション4に戻ると、その方向の何かが実際にずっと後で発生しました。かなり長い間名前を解決するときに、必要なクラスのすべての静的メンバーを検討するようコンパイラーに要求できます。 import static someclass.*;

余談ですが、他の言語には無料の関数(オプションで名前空間を使用)に対する偏見がないか、小さい型がはるかに少ないため、他の言語にはその問題はありません。

2
Deduplicator

(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同じモジュール/クラス/名前空間、たとえばスタンドアロン関数または静的メソッド)、「ヘルパー」のコレクションに配置することを選択する人もいます関数、サードパーティのライブラリに委任することを選択する場合があります。

2
Warbo