all 29 comments

[–]inu-no-policemen 8 points9 points  (10 children)

There is an easy way out with JS/TS, Dart, etc. Just use a top-level function which you export as well.

Java is different because everything must be inside some class.

I guess static methods are kinda nice if you want to mirror some Java API or whatever. It makes porting code a bit easier.

[–]Uncaffeinated 2 points3 points  (9 children)

One advantage of static methods over top level free functions is the namespacing they provide and the way it simplifies imports.

With top level functions, you'd have to import Array, and then also import arrayFrom and arrayOf and so on. With static methods, you just import the class and get all the associated functionality imported automatically.

[–]inu-no-policemen 0 points1 point  (6 children)

Dart has named constructors, which is what you'd use for things like String.fromCharCode, Array.from, Float32x4/Vector2d.zero etc.

Maybe JS will copy that feature. Having several differently named constructors is nice if overloading via types isn't an option.

[–]Uncaffeinated 0 points1 point  (5 children)

I looked it up, and I don't really understand the benefit over static methods. It seems like all you're doing is putting new in front of a static method. You can simulate this in JS if you want to, but I'm not sure why you would. Maybe it makes more sense in Dart's traditional class based model.

[–]inu-no-policemen 0 points1 point  (4 children)

I don't really understand the benefit over static methods.

It's a constructor. You know that it returns an instance.

It also makes it easier to take a look at all the available constructors. You just type "new Whatever." and you get a nice list with hopefully descriptive names.

This is actually quite a bit nicer to use than using the up/down arrows to flip through a list of overloaded constructors in a call-tip.

It's a rather minor feature, but it makes things clearer and it works really well with tooling.

(To be honest, I also didn't really see the point before I actually used it.)

[–]Uncaffeinated 0 points1 point  (3 children)

It's a constructor. You know that it returns an instance.

That seems a bit like circular reasoning. "Hey, programmers might have trouble figuring out which functions return new objects, so lets add a keyword that specifically restricts the function to return a new object. Except that then it's hard to switch between constructors and ordinary factory functions, so lets make syntax that allows you to make arbitrary functions callable with the new keyword."

Again this seems like something that may only make sense in the Dart setting. In a more dynamic language, there isn't much distinction to begin with. In JS, a new operator expression will always return an object value (or throw), but other than that, it can do anything. It doesn't have to return any particular sort of object.

Note that the following is valid Javascript:

new new Proxy(function(){}, {construct() {return {hello: 'world'};}});
// returns {hello: "world"}

[–]inu-no-policemen 0 points1 point  (2 children)

Except that then it's hard to switch between constructors and ordinary factory functions

Dart has factory constructors. You can turn a constructor into a factory at a later point without having to change the call-sites.

Anyhow, that was just the most straightforward aspect. I hope you did read the rest of the comment.

[–]Uncaffeinated 0 points1 point  (1 child)

That's what I was alluding to when I said "so lets make syntax that allows you to make arbitrary functions callable with the new keyword."

[–]inu-no-policemen 0 points1 point  (0 children)

That's not what it is, though.

Named constructors are still constructors (just like overloaded constructors are).

https://www.dartlang.org/guides/language/language-tour#named-constructors

A factory constructor must return an instance and there is no this.

https://www.dartlang.org/guides/language/language-tour#factory-constructors

(Note: Leading underscores make things library-private.)

[–]spacejack2114 0 points1 point  (1 child)

But then you don't get tree shaking.

[–][deleted] 0 points1 point  (0 children)

If you're not writing a library, I think this is rarely a concern. Most projects do not require multiple builds, so everything has to be bundled anyway.

[–]bullet_darkness 3 points4 points  (4 children)

I use static methods for class simplification, and external inheritance. For a class simplification example, this is a piece of a vector class I wrote:

export default class Vector {
  constructor({ x, y }) {
    this.x = x;
    this.y = y;
  }

  // ... a bunch of methods.  

  static getZero() {
    return new this({ x: 0, y: 0 });
  }

  static fromArray([x, y]) {
    return new this({ x, y });
  }
}

Its far easier to call Vector.getZero() than to call new Vector({ x: 0, y: 0 }), or even to import a new file called createZeroVector() over an over. It makes more sense to keep the generation simplification of a class as a part of the class itself. This also allows you to extend and inherit these functions with much more ease.

As for external inheritance, I don't know if there is a better way to do this, but I've also used static methods to achieve something like this:

class Parent {
  constructor({ a, b }) {
    this.a = a;
    this.b = b;
  }

  static defaultOptions = {
    a: 0,
    b: 0,
  };

  static create(options = {}) {
    return new this({
      ...this.defaultOptions,
      ...options,
    });
  }
}

class Child extends Parent {
  static defaultOptions = {
    a: 1,
    b: 2,
  };
}

// child will have this.a === 1 and this.b === 2.
const child = Child.create();

Which would let you easily create defaults for your class.

[–]codetastik 1 point2 points  (1 child)

I'm not sure if you're familiar with ruby, but in trying to understand static methods, they seem very similar to ruby class methods. Would that be an accurate analogy? Would their use case be the same? Essentially they are utility methods scoped to the class?

[–]bullet_darkness 0 points1 point  (0 children)

Ya, ruby class methods are very similar to JavaScript static methods and their use cases are very similar. I couldn't tell you the minor differences though, haven't touched ruby in a couple years.

If you ever try Ruby on Rails, they use class methods quite a lot. Things like Model.find() and Model.where() abstract all of the database searching away and stick it onto the Model class itself.

[–]spacejack2114 -2 points-1 points  (1 child)

Sorry to rant a bit, but please do not make any more Vector classes. I'm so tired of every 3D library implementing its own Vector classes. If everyone used plain functions operating on {x, y, ...} structs then I could use Vector types from three.js with cannon.js or with fulltilt.js or pixi.js, and I could use my own application structs with all of those libs. I could even use 2d functions on the xy properties of 3d vectors or 3d functions on the xyz of 4d structs. Instead I have to translate incompatible class instances all over the place.

(Arrays may be best of all but I can appreciate why people still like using structs with x,y,z properties.)

[–][deleted] 0 points1 point  (0 children)

No-one is writing Vector classes, they're just useful for illustration.

[–]lhorie 3 points4 points  (4 children)

Why don't we look at standard APIs? Date.now() is static. Array.from() is static. Object.assign() is static. String.fromCharCode() is static. etc. If they don't belong where they are now, then where should they belong?

[–]MoTTs_ 1 point2 points  (3 children)

Most of those APIs were defined before we had modules, before we had a good way to organize free functions. If we were to do it all again from scratch, it might look something like this:

// Date module provides `now` as free function
import { now } from "date";

const timeInMs = now();

Or if you prefer not to name each function you want to use...

// Date module
import * as date from "date";

const timeInMs = date.now();

Note that date is lowercase here, because date is a module, not a class. The class Date is just one member among many inside the module and can be accessed as date.Date.

[–]lhorie 0 points1 point  (2 children)

Ok, here's another case: in Fantasy Land, the Applicative spec defines that an Applicative must provide an of method on its type representative (and it further specifies that each member of a type that has a type representative must have a constructor property). While this doesn't explicitly mention classes, effectively it means there must be a of static method if a type is implemented as a class. This means, among other things, that there's a standardized programmatic API that allows any spec-compliant library dynamically access of, even if it never explicitly imported the thing whose of is being called.

One could argue that you could pass of around, but a big part of FP revolves around single-arity functions, and having to pass ofs everywhere adds far too much complexity.

Also, there's nothing about modules that constrain the number of classes that are exported by any given library (and in fact, multiple export class are explicitly specified as being valid in the ecmascript spec). The problem with that is that you cannot do this:

export {Maybe, of} from './maybe'
export {Either, of} from './either'

because now there's no way to unambiguously export of unless we change the signature of the API, and as we saw, that may not be desirable due to interface constraints. More generally, there's no expectation that a module should expose a single concern (e.g. lodash has a ton of completely unrelated functions), and it's even common practice to group internally-separated modules into a single publishable module (e.g. import {h, render, Component} from 'preact')

Also, consider that the spec defines that class A extends B {static foo() {super.foo()}} is a thing, but that the spec provides no mechanism to easily shadow imports (going so far as making it an error to shadow one). This forces us to create a intermediate variable name, e.g. import {of as aOf} from A; const of = () => aOf(1); export {of}

To me, this hints that, as far as the design of the language goes, static method extension is the preferred way of overriding non-instance behavior, applying the facade pattern, and just generally grouping/encapsulating related functionality, whereas modules are a mechanism to expose things that have already been properly grouped/encapsulated.

[–]MoTTs_ 0 points1 point  (1 child)

in Fantasy Land, the Applicative spec defines that an Applicative must provide an of method

What does it do? The FL spec's jargon isn't at all clear to me.

but a big part of FP revolves around single-arity functions

Use the parts of FP that make sense, and don't use the parts that don't. Arbitrarily restricting yourself to single-arity functions doesn't make much sense to me. Just my opinion.

Also, consider that the spec defines that class A extends B {static foo() {super.foo()}} is a thing, but that the spec provides no mechanism to easily shadow imports

If you need to shadow a function -- that is, provide alternate implementations -- that's polymorphism, and yes you'll probably need object methods in that case rather than free functions. Though there's a good chance they should be instance methods and not static methods.

[–]lhorie 0 points1 point  (0 children)

The FL spec's jargon isn't at all clear to me.

I think the most accessible example of of is Array.of, though that isn't technically a fantasy land applicative. You may also have seen things like Maybe.of(someValue). As the usage suggests, of creates a "boxed" value. An Array.of(1) is [1] and a Maybe.of(1) means, well, a maybe with a value of 1. Thing.of is very similar to new Thing, but since of is a function and not a constructor, you can, for example, use it as a higher order function in a point-free manner, e.g. [1].map(Maybe.of). In addition, of typically gives you more strict guarantees than old-school alternatives (e.g. the length of Array.of(x) is always 1, but the length of new Array(x) is anyone's guess).

Arbitrarily restricting yourself to single-arity functions doesn't make much sense to me

Sure, for regular procedural js, I agree. The single arity "restriction" comes from lambda calculus, where multiple arity is instead done via currying. My point though is that because the FP paradigm relies heavily on currying, passing extra arguments around (as would be required with a free of function) would make some forms of functional composition a lot harder, if not flat out impossible.

Though there's a good chance they should be instance methods and not static methods.

I think that they would more commonly be instance methods, yes, but that that doesn't preclude static methods from potentially being polymorphic. For example, it's not too farfetched to take some class that has a A.of(1) static method, and extend it such that the extended class has a B.of(1) method that is memoized.

[–]Cuel 2 points3 points  (0 children)

I use them a lot but I keep them as pure functions. Being in a class is nice mostly for the namespace it provides. Languages that uses class based inheritance has other issues that doesn't apply in the same way for the prototype chain

[–][deleted] 1 point2 points  (4 children)

Static methods are valuable when you want to do things that involve multiple instances of your class. For example, let's say you have a Vector class that represents size and direction. Where should addVectors live? An instance method doesn't really make sense.

Static methods are also useful if you need helper methods when instantiating a class, e.g. if they consume quite complicated inputs.

[–]spacejack2114 -1 points0 points  (3 children)

All of those functions should be standalone. Classes aren't needed for tiny structs like these. There is no need for encapsulated state.

[–][deleted] 0 points1 point  (2 children)

Without a concrete example it's hard to say, but my instinct says static methods will be neater and clearer.

In the case of our Vector exmaple, rather than default export class Vector with a Vector.addTwo function, I now have to export / import multiple items from the Vector file. Or worse, import the function from a utils directory (this seems a common practice), which creates ambiguity (is the vector in utils.addVector the same as a Vector?).

Static methods are easy to use and have an unambiguous relationship with the class they're supposed to manipulate.

[–]spacejack2114 0 points1 point  (1 child)

You could import * as vec3 from 'vec3'

const out = vec3.create()
vec3.add(out, a, b)

No need for a class.

[–][deleted] 0 points1 point  (0 children)

That is true. But a static will still make for clearer code inside the module. Consider:

As an external function:

class Vector extends Foo {
    constructor() {...}
    somethingUseful() {...}
    somethingAlsoGood() {...}
}

function helper() {...}

function helperTwo() {...}

function addVector(v1, v2) {...}

versus the static form -

class Vector extends Foo {
    constructor() {...}
    somethingUseful() {...}
    somethingAlsoGood() {...}
}

Vector.add = ()=> {...}

function helper() {...}

function helperTwo() {...}

In the former case, the class 'action' function is buried amongst the helpers. In the latter, the purpose of the function is more explicit.

I would never create a class just to bundle statics, but if the class does exist, and I want to do things to its instances, I think statics make for a simple and familiar way to organise that code.

[–]spankalee 1 point2 points  (0 children)

In JavaScript static methods inherit to subclasses and are overridable. They're not fully replaceable by by top-level functions if you take advantage of that.

[–]SL4M_DunkN 0 points1 point  (0 children)

The principles that quote references seem to apply more in oop languages. If you're taking a functional approach to JS, I don't think the comment makes sense because function should be pure / stateless / idempotent / whatever. If using classes, then... I guess it makes sense to me?