30

In a discussion about static and instance methods, I always think, that Sqrt() should be a instance method of number types instead of a static method. Why is that? It obviously works on a value.

 // looks wrong to me
 var y = Math.Sqrt(x);
 // looks better to me
 var y = x.Sqrt();

Value types obviously can have instance methods, as in many languages, there is an instance method ToString().

To answer some questions from the comments: Why should 1.Sqrt() not be legal? 1.ToString() is.

Some languages do not allow to have methods on value types, but some languages can. I am talking about these, including Java, ECMAScript, C# and Python (with __str__(self)defined). The same applies to other functions like ceil(), floor() etc.

  • 18
    What language are you proposing this in? Would 1.sqrt() be valid? – user40980 Nov 3 '15 at 14:07
  • 20
    In many languages (e.g. java) doubles are primatives (for performance reasons) so they don't have methods – Richard Tingle Nov 3 '15 at 14:10
  • 45
    So numeric types should be bloated with every possible mathematical function that could be applied to them? – D Stanley Nov 3 '15 at 14:24
  • 18
    FWIW I think Sqrt(x) looks much more natural than x.Sqrt() If that means prepending the function with the class in some languages I'm OK with that. If it were an instance method then x.GetSqrt() would be more appropriate to indicate that it's returning a value rather than modifying the instance. – D Stanley Nov 3 '15 at 14:28
  • 23
    This question can not be language agnostic in its current form. That's the root of the problem. – risingDarkness Nov 3 '15 at 14:41

10 Answers 10

19

It is entirely a choice of language design. It also depends on the underlying implementation of primitive types, and performance considerations due to that.

.NET has just one static Math.Sqrt method that acts on a double and returns a double. Anything else you pass to it must be cast or promoted to a double.

double sqrt2 = Math.Sqrt(2d);

On the other hand, you have Rust which exposes these operations as functions on the types:

let sqrt2 = 2.0f32.sqrt();
let higher = 2.0f32.max(3.0f32);

But Rust also has universal function call syntax (someone mentioned that earlier), so you can choose whatever you like.

let sqrt2 = f32::sqrt(2.0f32);
let higher = f32::max(2.0f32, 3.0f32);
  • 1
    It's worth noting that in .NET you can write extension methods, so if you really want to get this implementation to look like x.Sqrt(), it can be done. public static class DoubleExtensions { public static double Sqrt( this double self) { return Math.Sqrt(self); } } – Zachary Dow Nov 5 '15 at 19:32
  • 1
    Also in C# 6 it can be just Sqrt(x) msdn.microsoft.com/en-us/library/sf0df423.aspx. – Den Nov 6 '15 at 11:35
65

Suppose we're designing a new language and we want Sqrt to be an instance method. So we look at the double class and begin designing. It obviously has no inputs (other than the instance) and returns a double. We write and test the code. Perfection.

But taking the square root of an integer is valid, too, and we don't want to force everyone to convert to a double just to take a square root. So we move to int and start designing. What does it return? We could return an int and make it work only for perfect squares, or round the result to the nearest int (ignoring the debate about the proper rounding method for now). But what if someone wants a non-integer result? Should we have two methods - one that returns an int and one that returns a double (which is not possible in some languages without changing the name). So we decide that it should return a double. Now we implement. But the implementation is identical to the one we used for double. Do we copy-and-paste? Do we cast the instance to a double and call that instance method? Why not put the logic in a library method that can be accessed from both classes. We'll call the library Math and the function Math.Sqrt.

Why is Math.Sqrt a static function?:

  • Because the implementation is the same regardless of the underlying numeric type
  • Because it does not affect a particular instance (it takes in one value and returns a result)
  • Because numeric types do not depend on that functionality, therefore it makes sense to have it in a separate class

We haven't even addressed other arguments:

  • Should it be named GetSqrt since it returns a new value rather than modifying the instance?
  • What about Square? Abs? Trunc? Log10? Ln? Power? Factorial? Sin? Cos? ArcTan?
  • 6
    Not to mention the joys of 1.sqrt() vs 1.1.sqrt() (ghads, that looks ugly) do they have a common base class? What is the contract for its sqrt() method? – user40980 Nov 3 '15 at 14:47
  • 5
    @MichaelT Nice example. It took me four reads to understand what 1.1.Sqrt represented. Clever. – D Stanley Nov 3 '15 at 14:50
  • 17
    I'm not really clear from this answer how the static class helps with your main reason. If you have double Sqrt(int) and double Sqrt(double) on your Math class, you have two options: convert the int to a double, then call into the double version, or copy and paste the method with appropriate changes (if any). But those are exactly the same options you described for the instance version. Your other reasoning (particularly your third bullet point) I agree with more. – Ben Aaronson Nov 3 '15 at 17:03
  • 14
    -1 this answer is absurd, what does any of this have to do with being static? You're going going to have decide answers to those same questions either way (and "[with a static function] the implementation is the same" is false, or at least no more true than it would be for instance methods..) – BlueRaja - Danny Pflughoeft Nov 3 '15 at 21:52
  • 20
    "The implementation is the same regardless of the underlying numeric type" is utter nonsense. Implementations of the square root function need to differ significantly based on the type they're working with in order not to be horribly inefficient. – R.. Nov 4 '15 at 3:37
25

Mathematical operations are often very performance-sensitive. Therefore, we will want to use static methods that can be fully resolved (and optimizied, or inlined) at compile time. Some languages do not offer any mechanism to specify statically dispatched methods. Furthermore, the object model of many languages has considerable memory overhead that is unacceptable for “primitive” types such as double.

A few languages allow us to define functions that use method invocation syntax, but are actually dispatched statically. Extension methods in C# 3.0 or later are an example. Non-virtual methods (e.g. the default for methods in C++) are another case, though C++ does not support methods on primitive types. You could of course create your own wrapper class in C++ that decorates a primitive type with various methods, without any runtime overhead. However, you will have to manually convert values to that wrapper type.

There are a couple of languages that do define methods on their numeric types. These are usually highly dynamic languages where everything is an object. Here, performance is a secondary consideration to conceptual elegance, but those languages are not generally used for number crunching. However, these languages might have an optimizer that can “unbox” operations on primitives.


With the technical considerations out of the way, we can consider whether such a method-based math interface would be a good interface. Two issues arise:

  • mathematical notation is based on operators and functions, not on methods. An expression such as 42.sqrt will appear much more alien to many users than sqrt(42). As a math-heavy user, I'd rather prefer the ability to create my own operators over dot-method-call syntax.
  • the Single Responsibility Principle encourages us to limit the number of operations that are part of a type to the essential operations. Compared with multiplication, you need the square root remarkably rarely. If your language is specifically intended for statistical anlysis, then providing more primitives (such as operations mean, median, variance, std, normalize on numeric lists, or the Gamma function for numbers) can be useful. For a general-purpose language, this just weighs down the interface. Relegating non-essential operations to a separate namespace makes the type more accessible for the majority of users.
  • Python is a good example of an everything-is-an-object language used for heavy number crunching. NumPy scalars actually have dozens and dozens of methods, but sqrt still isn't one of them. Most of them are things like transpose and mean that are only there to provide a uniform interface with NumPy arrays, which are the real workhorse data structure. – user2357112 Nov 3 '15 at 18:04
  • 7
    @user2357112: The thing is, NumPy itself is written in a mixture of C and Cython, with some Python glue. Otherwise it could never be as fast as it is. – Kevin Nov 4 '15 at 6:42
  • 1
    I think this answer hits pretty closely to some kind of real-world compromise that has been reached over the years of design. In other news, does it really make sense in .Net to be able to do "Hello World".Max() as the LINQ extensions allows us AND makes very visibile in Intellisense. Bonus points: What is the result? Bonus Bonus, what is the result in Unicode...? – Andyz Smith Nov 4 '15 at 15:15
15

I would be motivated by the fact that there's a ton of special-purpose math functions, and rather than populate every math type with all (or a random subset) of those functions you put them in a utility class. Otherwise, you'd either pollute your auto-completion tooltip, or you'd force people to always look in two places. (Is sin important enough to be a member of Double, or is it in the Math class along with inbreds like htan and exp1p?)

Another practical reason is that it turns out there may be different ways to implement numerical methods, with different performance and precision trade-offs. Java has Math, and it also has StrictMath.

  • I would hope language designers don't care about auto-complete tooltips. As well, what happens on Math.<^space>? That auto-complete tooltip will also be polluted. Inversely, I think your second paragraph is probably one of the better answers here. – Qix Sep 29 '16 at 18:53
  • @Qix They do. Although, other people here might call it "making an interface bloated." – Aleksandr Dubinsky Sep 30 '16 at 0:12
6

You have correctly observed that there is a curious symmetry at play here.

Whether I say sqrt(n) or n.sqrt() does not really matter, they both express the same thing and which one you prefer is more a matter of personal taste than anything else.

That is also why there is a strong argument from certain language designers to make the two syntaxes interchangeable. The D programming language already allows this under a feature called Uniform Function Call Syntax. A similar feature has also been proposed for standardization in C++. As Mark Amery points out in the comments, Python allows this too.

This is not without problems. Introducing a fundamental syntax change like this has wide-ranging consequences for existing code and is of course also a topic of controversial discussions among developers who have been trained for decades to think of the the two syntaxes as describing different things.

I guess only time will tell whether the unification of the two is feasible in the long run, but it is definitely an interesting consideration.

  • Python already supports both of these syntaxes. Every non-static method takes self as its first parameter, and when you call the method as a property of an instance, instead of as a property of the class, the instance gets implicitly passed as the first argument. Hence I can write "foo".startswith("f") or str.startswith("foo", "f"), and I can write my_list.append(x) or list.append(my_list, x). – Mark Amery Nov 5 '15 at 15:14
  • @MarkAmery Good point. This is not quite as drastic as what D or the C++ proposals do, but it fits the general idea. Thanks for pointing out! – ComicSansMS Nov 5 '15 at 15:41
3

In addition to the answer of D Stanley you have to think about polymorphism. Methods like Math.Sqrt should always return the same value to the same input. Making the method static is a good way to make this point clear, since static methods are not overrideable.

You mentioned the ToString()-method. Here you may want to override this method, so the (sub)class is represented in an other way as String as its parent class. So you make it an instance Method.

2

Well, in Java there is a wrapper for every basic type.
And basic types are not class-types, and have no member-functions.

So, you have the following choices:

  1. Collect all those helper-functions into a pro-forma-class like Math.
  2. Make it a static function on the corresponding wrapper.
  3. Make it a member-function on the corresponding wrapper.
  4. Change the rules of Java.

Let's rule option 4 out, because... Java is Java, and adherents profess to like it that way.

Now, we can also rule out option 3 because while allocating objects is fairly cheap, it isn't free, and doing that over and over again does add up.

Two down, one still to kill: Option 2 is also a bad idea, because it means every function must be implemented for every type, one cannot rely on widening conversion to fill the gaps, or the inconsistencies will really hurt.
And taking a look at java.lang.Math, there are lots of gaps, especially for types smaller than int respective double.

So, in the end the clear victor is option one, collecting them all in one place in a utility-function-class.

Returning to option 4, something in that direction actually happened much later: You can ask the compiler to consider all the static members of any class you want when resolving names for quite a long time now. import static someclass.*;

As an aside, other languages don't have that problem, either because they have no prejudice against free functions (optionally using namespaces) or far fewer small types.

  • 1
    Consider the joys of implementing the variations on Math.min() in all the wrapper types. – user40980 Nov 3 '15 at 22:51
  • I find #4 unconvincing. Math.sqrt() was created at the same time as the rest of Java, so when the decision was made to put sqrt() in Math there was no historical inertia of Java users who "like it that way". While there isn't much of a problem with sqrt(), the overloading behavior of Math.round() is atrocious. Being able to use member syntax with values of type float and double would have avoided that problem. – supercat Feb 13 '16 at 3:18
2

One point that I don't see mentioned explicitly (although amon alludes to it) is that square root can be thought of as a "derived" operation: if the implementation doesn't provide it for us, we can write our own.

Since the question is tagged with language-design, we might consider some language-agnostic description. Although many languages have different philosophies, it is very common across paradigms to use encapsulation to preserve invariants; i.e. to avoid having a value which doesn't behave as its type would suggest.

For example, if we have some implementation of integers using machine words, we probably want to encapsulate the representation somehow (e.g. to prevent bit shifts from changing the sign), but at the same time we still need access to those bits to implement operations like addition.

Some languages may implement this with classes and private methods:

class Int {
    public Int add(Int x) {
      // Do something with the bits
    }
    private List<Boolean> getBits() {
      // ...
    }
}

Some with module systems:

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

Some with lexical scope:

(defun getAdder ()
   (let ((getBits (lambda (x) ; ...
         (add     (lambda (x y) ; Do something with the bits
     'add))

And so on. However, none of these mechanisms are needed for implementing square root: it can be implemented using the public interface of a numeric type, and hence it doesn't need access to the encapsulated implementation details.

Hence the location of square root comes down to the philosophy/tastes of the language, and of the library designer. Some may choose to put it "inside" the numeric values (e.g. make it an instance method), some may choose to put it at the same level as the primitive operations (this might mean an instance method, or it might mean living outside the numeric values, but inside the same module/class/namespace, e.g. as a standalone function or static method), some might choose to put it in a collection of "helper" functions, some might choose to delegate it to third-party libraries.

  • Nothing would prevent a language from allowing a static method to be called either using member syntax (as with C# or vb.net extension methods) or with a variation of member sequence (I would have liked to have seen a double-dot syntax, so as to allow the Intellisense advantages of being able to list only functions that were suitable for the primary argument, but avoid ambiguity with real member operators). – supercat Feb 13 '16 at 3:15
-2

In Java and C# ToString is a method of object, the root of the class hierarchy, so every object will implement the ToString method. For an Integertype it's just natural that the implementation of ToString would work this way.

So you reasoning is wrong. The reason value types implement ToString is not that some people were like: hey let's have a ToString method for Value types. It's because ToString is already there and it's "the most natural" thing to output.

  • 1
    Of course it is a decision, i.e. the decision to have object have a ToString() method. That is in your words "some people were like: hey let's have a ToString method for Value types". – Residuum Nov 4 '15 at 13:15
-3

Unlike String.substring, Number.sqrt is not really an attribute of the number but rather a new result based on your number. I do think passing your number to a squaring function is more intuitive.

Moreover, the Math object contains other static members and it makes more sense to bunch them together and use them in a uniform manner.

protected by Community Nov 4 '15 at 13:58

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.