为什么Math.Sqrt()是静态函数?


31

在讨论静态和实例方法时,我总是认为,这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(已__str__(self)定义)。这同样适用于其他功能,例如ceil()floor()等等。


18
您打算用哪种语言?会1.sqrt()有效吗?

20
在许多语言中(例如Java),双精度数是基元(出于性能原因),因此它们没有方法
Richard Tingle 2015年

45
那么数字类型应该被所有可能应用于它们的数学函数mathematical肿吗?
D Stanley

19
FWIW我觉得Sqrt(x)看起来很多比更自然x.Sqrt() 是否意味着前面加上功能在某些语言,我与OK的类。如果它一个实例方法,则x.GetSqrt()表明它正在返回值而不是修改实例会更合适。
D Stanley

23
此问题在当前形式下不能与语言无关。这就是问题的根源
上升黑暗

Answers:


20

这完全是语言设计的选择。它还取决于基本类型的基础实现以及相应的性能考虑。

.NET只有一个Math.Sqrt作用于a double并返回a的静态方法double。您传递给它的任何其他内容都必须投射或提升为double

double sqrt2 = Math.Sqrt(2d);

另一方面,您拥有Rust,将这些操作作为函数公开给类型

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);

1
值得注意的是,在.NET中,您可以编写扩展方法,因此,如果您确实希望使该实现看起来像x.Sqrt(),则可以完成。public static class DoubleExtensions { public static double Sqrt( this double self) { return Math.Sqrt(self); } }
Zachary Dow

1
同样在C#6中,它可能只是Sqrt(x) msdn.microsoft.com/en-us/library/sf0df423.aspx

65

假设我们正在设计一种新的语言,并且想Sqrt成为一个实例方法。因此,我们着眼于double课堂并开始设计。它显然没有输入(实例除外),并返回double。我们编写并测试代码。完美。

但是,取整数的平方根也是有效的,我们不想强迫每个人仅取平方根就转换为双精度数。因此,我们int开始进行设计。它返回什么?我们可以返回,int并使它仅适用于完美的正方形,或者将结果四舍五入到最接近的值int(现在不考虑关于适当的舍入方法的争论)。但是,如果有人想要一个非整数的结果呢?我们是否应该有两种方法-一种返回an,int而另一种则返回a double(在某些语言中,不更改名称是不可能的)。因此,我们决定应返回double。现在我们执行。但是实现与我们以前使用的实现相同double。我们会复制粘贴吗?我们是否将实例转换为double并调用实例方法?为什么不将逻辑放在可以从这两个类访问的库方法中。我们将调用库Math和函数Math.Sqrt

为什么是Math.Sqrt静态函数?:

  • 因为实现是相同的,而不管基础数字类型如何
  • 因为它不影响特定实例(它接受一个值并返回结果)
  • 由于数字类型不依赖于该功能,因此将其放在单独的类中是有意义的

我们甚至没有解决其他论点:

  • 它应该命名,GetSqrt因为它返回一个新值而不是修改实例吗?
  • SquareAbsTruncLog10LnPowerFactorialSinCosArcTan

6
更不用说1.sqrt()vs 1.1.sqrt()(令人讨厌的东西,丑陋的)了,他们有一个共同的基础阶级吗?它的sqrt()方法是什么合同?

5
@MichaelT尼斯的例子。我花了四读才能理解1.1.Sqrt代表什么。聪明。
D Stanley

17
从这个答案中我不清楚,静态类如何帮助您解决主要原因。如果您在Math类上具有double Sqrt(int)和double Sqrt(double),则有两个选择:将int转换为double,然后调用double版本,或复制并粘贴具有适当更改的方法(如果有) )。但是这些选项与您为实例版本描述的选项完全相同。您的其他推理(尤其是您的第三个要点)我也同意。
Ben Aaronson

14
-1这个答案是荒谬的,是什么任何的这种具有静态的呢?您打算将不得不决定回答这些同样的问题两种方式(和“[一个静态函数]实行的是相同的”是假的,或者至少没有更真实比起来,例如方法..)
BlueRaja-Danny Pflughoeft 2015年

20
毫无意义的是“无论底层数字类型如何,实现都是相同的”。平方根函数的实现必须根据所使用的类型而有显着差异,以免效率低下。
R..

25

数学运算通常对性能非常敏感。因此,我们将要使用可以在编译时完全解决(优化或内联)的静态方法。某些语言不提供任何机制来指定静态分派的方法。此外,许多语言的对象模型都有相当大的内存开销,这对于诸如的“原始”类型是不可接受的double

几种语言允许我们定义使用方法调用语法的函数,但实际上是静态分配的。C#3.0或更高版本中的扩展方法是一个示例。非虚拟方法(例如,C ++中方法的默认方法)是另一种情况,尽管C ++不支持原始类型的方法。您当然可以在C ++中创建自己的包装器类,该包装器类使用各种方法装饰原始类型,而没有任何运行时开销。但是,您将必须手动将值转换为该包装器类型。

有几种语言确实在其数字类型上定义了方法。这些通常是高度动态的语言,其中的一切都是对象。在这里,性能是概念上的优雅的次要考虑因素,但是这些语言通常不用于数字运算。但是,这些语言可能有一个优化程序,可以对原语进行“拆箱”操作。


出于对技术的考虑,我们可以考虑这种基于方法的数学接口是否是一个好的接口。出现两个问题:

  • 数学符号是基于运算符和函数,而不是方法。诸如这样的表达式42.sqrt对许多用户而言比更为陌生sqrt(42)。作为具有大量数学运算的用户,我宁愿使用点运算法则语法来创建自己的运算符的功能。
  • “单一责任原则”鼓励我们将属于某种类型的操作的数量限制为基本操作。与乘法相比,很少需要平方根。如果你的语言是专门用于统计anlysis,然后提供更多的原语(如操作meanmedianvariancestdnormalize在数字列表,或对数字伽玛功能)是有用的。对于通用语言,这只会降低界面的权重。将非必要操作委托给单独的名称空间,可使大多数用户更容易访问该类型。

Python是用于大量运算的“万物是一种目标”语言的一个很好的例子。NumPy标量实际上有数十种方法,但sqrt仍然不是其中一种。他们大多是类似的东西transpose,并mean认为只有在那里提供NumPy的阵列,这是真正的主力数据结构的统一接口。
user2357112支持Monica

7
@ user2357112:问题是,NumPy本身是用C和Cython的混合物编写的,并带有一些Python胶水。否则,它永远不可能像现在这样快。
凯文

1
我认为这个答案非常接近多年来设计中已经达到的某种现实世界的折衷。其他消息方面,它真正意义在.NET中能够做到的“Hello World”。最大()作为LINQ的扩展,使我们在智能感知让人很都看得到。奖励积分:结果如何?红利红利,Unicode的结果是什么?
Andyz Smith

15

我会被大量特殊用途的数学函数所吸引,而不是用将它们放入实用程序类的所有(或随机子集)函数填充每种数学类型。否则,您要么污染自动完成工具提示,要么强迫人们始终在两个地方查看。(sin成为会员很重要Double,还是和and等Math近亲一起在班上?)htanexp1p

另一个实际的原因是,事实证明,可能存在不同的方法来实现数值方法,但性能和精度之间会有不同。Java有Math,它也有StrictMath


我希望语言设计人员不要在意自动完成的工具提示。同样,发生了Math.<^space>什么?该自动完成工具提示也会受到污染。相反,我认为您的第二段可能是这里的更好答案之一。
Qix

@Qix他们会的。虽然,这里的其他人可能会称其为“使接口膨胀”。
Aleksandr Dubinsky

6

您已经正确地观察到这里存在一个奇怪的对称性。

不管我说sqrt(n)还是n.sqrt()不重要,它们都表达相同的东西,而您更喜欢哪一个更关乎个人喜好。

这也是为什么某些语言设计人员强烈主张使两种语法可互换的原因。D编程语言已经在称为“ 统一函数调用语法”的功能下允许此操作。对于C ++中的标准化,提出了类似的功能。正如Mark Amery在评论中指出的那样,Python也允许这样做。

这并非没有问题。引入这样的基本语法更改会对现有代码产生广泛的后果,当然,这也是经过数十年培训以考虑这两种语法描述不同事物的开发人员之间有争议的话题。

我想只有时间才能证明两者的统一从长远来看是否可行,但这绝对是一个有趣的考虑。


Python已经支持这两种语法。每个非静态方法都self将其作为第一个参数,并且当您将该方法作为实例的属性而不是类的属性调用时,该实例将作为第一个参数隐式传递。因此,我可以写"foo".startswith("f")str.startswith("foo", "f"),也可以写my_list.append(x)list.append(my_list, x)
Mark Amery

@MarkAmery好点。这并不像D或C ++提案那么激烈,但它符合一般的想法。感谢您指出!
ComicSansMS 2015年

3

除了D Stanley的答案之外,您还必须考虑多态性。诸如Math.Sqrt之类的方法应始终将相同的值返回到相同的输入。将方法设为静态是明确说明这一点的好方法,因为静态方法不可重写。

您提到了ToString()方法。在这里,您可能想覆盖此方法,因此(子)类以另一种方式表示为String作为其父类。因此,您将其设为实例方法。


2

好吧,在Java中,每种基本类型都有一个包装器。
基本类型不是类类型,并且没有成员函数。

因此,您有以下选择:

  1. 将所有这些辅助功能收集到一个备考类中,例如Math
  2. 在相应的包装器上使其成为静态函数。
  3. 使它成为相应包装上的成员函数。
  4. 更改Java规则。

让我们排除选项4,因为……Java是Java,而信奉者则自称喜欢这种方式。

现在,我们还可以排除选项3,因为虽然分配对象相当便宜,但它不是免费的,而且一遍又一遍地这样做确实会加起来。

两个缺点,一个仍然要杀死:选项2也不是个好主意,因为它意味着必须为每种类型实现每个函数,不能依靠扩大转换来填补空白,否则不一致会真正受到伤害。 再来看一下,还有很多空白,尤其是对于小于各自的类型。
java.lang.Mathintdouble

因此,最后明确的胜利者是第一个选择,将它们全部收集在一个实用功能类中。

回到选项4,实际上发生了很多事情:您可以要求编译器在很长一段时间内解析名称时,考虑您想要的任何类的所有静态成员。 import static someclass.*;

顺便说一句,其他语言没有这个问题,因为它们对自由函数(可选地使用名称空间)或更少的小类型没有偏见。


1
考虑Math.min()在所有包装类型上实现变体的乐趣。

我发现#4令人信服。 Math.sqrt()是与Java的其余部分同时创建的,因此,当决定将sqrt()放入时Math,“喜欢这种方式”的Java用户就没有历史惯性。尽管的问题不多sqrt(),但的重载行为Math.round()是残酷的。能够使用具有类型值的成员语法,float并且double可以避免该问题。
超级猫

2

我没有看到明确提到的一点(尽管阿蒙(Amon)暗示了这一点)是可以将平方根视为“派生”操作:如果实现未为我们提供,则可以编写自己的操作。

由于问题是用语言设计标记的,因此我们可以考虑一些与语言无关的描述。尽管许多语言有不同的理念,但是在跨范例中使用封装来保存不变式是很常见的。即,避免具有一个不像其类型提示的值。

例如,如果我们使用机器字对整数进行某种实现,则可能要以某种方式封装表示形式(例如,防止移位改变符号),但是与此同时,我们仍然需要访问这些位以实现诸如加成。

某些语言可以使用类和私有方法来实现此目的:

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))

等等。然而,没有一个需要用于执行平方根这些机制的:它可以使用来实现数字类型公共接口,因此不需要访问封装的实现细节。

因此,平方根的位置取决于语言和图书馆设计师的理念/品味。有些人可能选择将其放在数值的“内部”(例如,使其成为实例方法),有些人可能选择将其与原始操作放在同一级别(这可能意味着是实例方法,或者可能意味着居住数值之外)。数字值,但相同的模块/类/命名空间内(例如,作为独立函数或静态方法),有些人可能选择将其放入“帮助”函数的集合中,有些人可能选择将其委托给第三方库。


没有什么能阻止一种语言允许使用成员语法(如C#或vb.net扩展方法)或成员序列的变体(我希望看到双点语法)来调用静态方法。这样就具有Intellisense的优势,即能够仅列出适用于主要参数的函数,但避免与实际成员运算符产生歧义。
超级猫

-2

在Java和C#中,ToString是对象的方法,是类层次结构的根,因此每个对象都将实现ToString方法。对于Integertype,很自然的是ToString的实现将以这种方式工作。

所以你的推理是错误的。值类型实现ToString的原因并不像某些人那样:嘿,让我们为Value类型提供一个ToString方法。这是因为ToString已经存在,这是“最自然”的输出。


1
当然,这是一个决定,即object拥有某种ToString()方法的决定。那就是用您的话说:“有些人就像:嘿,让我们有一个用于值类型的ToString方法”。
Residuum 2015年

-3

与String.substring不同,Number.sqrt实际上不是数字的属性,而是基于您的数字的新结果。我确实认为将您的数字传递给平方函数会更直观。

此外,Math对象包含其他静态成员,将它们捆绑在一起并以统一的方式使用它们更有意义。


3
反例:BigInteger.pow()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.