tc39 / ecma262

Status, process, and documents for ECMA-262
https://tc39.es/ecma262/
Other
15.06k stars 1.29k forks source link

definition of "method" #2846

Open jmdyck opened 2 years ago

jmdyck commented 2 years ago

4.4.37 defines "method" as "function that is the value of a property", which includes just about every intrinsic function. So that definition should probably change.

Originally posted by @jmdyck in https://github.com/tc39/ecma262/issues/2576#issuecomment-968193862

We could say "function that is the value of a property of a prototype object". That wouldn't handle [Typed]Array.{from,of}, which don't satisfy that definition, but are described as methods. Still, it's pretty close.

ljharb commented 2 years ago

I’d say a method is any function-valued property that pays attention to its receiver, whether static or instance.

jmdyck commented 2 years ago

Could you rephrase that in terms that the spec defines?

ljharb commented 2 years ago

Which term there does the spec not define? receiver is “this value”, static means “on a constructor”, instance means “on a prototype object”.

jmdyck commented 2 years ago

Which term there does the spec not define?

It doesn't define:

receiver is “this value”, static means “on a constructor”, instance means “on a prototype object”.

Okay, so why not use those replacements/expansions instead?

ljharb commented 2 years ago

Sure:

A method is a function stored in any object property whose behavior changes based on its this value.

jmdyck commented 2 years ago

Much better.

The "stored in" phrase is unusual for the spec. The reader would probably understand that the function in question can be the [[Value]] of a data property, but it's unclear if it can be the [[Get]] or [[Set]] of an accessor property.

In practice, it looks like the spec never refers to accessor functions as "methods". (And that's not due to a #2592 normalization, it goes way back.) I'm guessing that's intentional rather than an oversight, so I'd suggest changing:

a function stored in any object property

to something like:

a function that is the value of a data property

Also, I'd suggest replacing:

behavior changes based on its this value

to:

behavior depends on its this value

(My thinking is that "behavior" refers to the totality of a function's input/output effect, which doesn't change.)

So that would be: [A method is a] function that is the value of a data property whose behavior depends on its this value

(Either way, it's a bit odd that the "whose" seems to attach to "property", whereas it actually attaches to "function". But I don't think that requires fixing.)

ljharb commented 2 years ago

Sounds good; your corrections still read clearly to me!

raulsebastianmihaila commented 2 years ago

I think you're overcomplicating things. If a child asks their parent what 'rain' is, the parent may say that it's 'fluid water that falls from the clouds'. While rain is water, an important aspect is the relationship between that water and the clouds. Whether a function depends on its receiver is irrelevant when speaking about methods. I remember that in the past you wouldn't be able to call console.log without the console receiver. This changed in the mean time, but the effect of the log function printing something to the console is the same. We would still say that it's a method of the console object because of the way we access it, meaning because of its relationship with object console. The relationship with objects is not intrinsic to the log function so it's not always relevant, we may put the log function it in a variable and refer to it as the 'log function'. But we speak of 'methods' when we consider them in certain relations to objects, for instance when we access the log function through a [[Get]] access on the console object.

Regarding accessor properties, I think that's also irrelevant, because from a property relationship perspective the distinction is only relevant in terms of the property attributes. In the context of methods (when we consider the relationship between objects and functions), data and accessor properties are the same with respect to the result of the [[Get]], [[HasProperty]] and [[OwnPropertyKeys]] operations. Going into more details and distinguishing between properties based on [[GetOwnProperty]], for instance, only overcomplicates things. I like how simply the spec currently defines methods and I think the best would be to leave it as is.

ljharb commented 2 years ago

In the console example, console.log isn’t a method anymore - but it used to be. Now it’s just a function in a namespace.

mhofman commented 2 years ago

Wondering if it'd complicate definitions to refer to things like console.log as a bound method? When you consider Node.js where you can create multiple Console instances, log is clearly a method related to that specific console instance, but it's usable as a function (without a receiver) because it's bound.

raulsebastianmihaila commented 2 years ago

In the console example, console.log isn’t a method anymore - but it used to be. Now it’s just a function in a namespace.

You're using your own discretionary definition of the word 'method' in contradiction with the spec.

ljharb commented 2 years ago

@raulsebastianmihaila the spec's definition isn't what's actually important tho - the colloquial one is, and it largely matches the definition I've provided.

raulsebastianmihaila commented 2 years ago

One traditional and very familiar to experienced JS programmers is this style of creating objects:

function Car() {
  let speed = 0;
  let accelerationRate = 5;

  return {
    get speed() {
      return speed;
    },

    accelerate() {
      speed += accelerationRate;
    },

    decelerate() {
      speed -= accelerationRate;
    }
  };
}

const car = new Car();

car.accelerate();
car.accelerate();
car.accelerate();
car.decelerate();

console.log(car.speed);

Colloquially, and also officially, accelerate and decelerate are methods and they don't use any receiver.

ljharb commented 2 years ago

To be fair tho, that module pattern was popular so long ago that there is an entire cohort of very experienced JS developers that have never used it.

In this case I'd actually say they aren't methods - specifically, they can be destructured off and called bare, and they'll still work. They're just pretending to be methods, but they're really just namespaced functions.

raulsebastianmihaila commented 2 years ago

That's very relative, for instance I wouldn't be surprised if those very experienced JS developers you speak of have never opened the spec. Anyway, would you agree that when that pattern was popular, accelerate and decelerate were considered methods and since then you decided on your own to change the meaning of the word 'method'? If you take a look at some JS books from that 'era' (because, you see, it was so long ago...) you would also find in that pattern the notion of 'private methods', meaning functions defined in the context of that constructor which are not exposed publicly. So, be sure you're contradicting existing books on JS patterns.

As to destructuring, there's an obvious reason not to do that:

const car1 = new Car();
const car2 = new Car();

car1.accelerate();
car2.accelerate();

is much better than

const {accelerate: accelerate1} = new Car();
const {accelerate: accelerate2} = new Car();

accelerate1();
accelerate2();
ljharb commented 2 years ago

Sure. That doesn't mean the spec needs to adhere to a definition that includes an obsolete practice, though.

mhofman commented 2 years ago

I would consider accelerate and decelerate to be bound methods (by way of the object as closure pattern). They apply to the car instance but can be used as free functions.

And I wouldn't consider that pattern as obsolete (however we prefer to name these functions "makers", and avoid calling them with new)

raulsebastianmihaila commented 2 years ago

That doesn't mean the spec needs to adhere to a definition that includes an obsolete practice

I don't agree that it's obsolete. It has advantages over post-ES5 classes for instance:

raulsebastianmihaila commented 2 years ago

we prefer to name these functions "makers", and avoid calling them with new

Is this the position of TC39?

ljharb commented 2 years ago

However, your "methods" aren't shared between instances - you have N copies of each function when you have N instances. That pattern was a hack around not having a good declarative way to create a constructor and prototype - class exists now, which makes it (7+ years now) obsolete even if it still has advantages.

mhofman commented 2 years ago

Is this the position of TC39?

Sorry for the confusion, no. This is our coding style at Agoric.

raulsebastianmihaila commented 2 years ago

you have N copies of each function when you have N instances

You can say it's not a perfect pattern, however nowadays people go even further and they create N copies of functions with React hooks on each rerender. I think the sense in which you use the word 'obsolete' is more like 'not trendy', which I think is a strange way of looking at things, given that the pattern has important semantic advantages.

zakarialaoui10 commented 2 years ago

is a property containing a function