Function constants for Identity and No-op

# Eli Perelman (8 years ago)

I'm not sure if something like this has been proposed before, but I often find myself declaring constants for the identity function and the no-op function. I think it would be useful to have constants available on Function containing these values. For example Function.IDENTITY and Function.NOOP. Essentially these constants would map to:

Function.IDENTITY = (a) => a;

Function.NOOP = () => null; // or:

Function.NOOP = () => {};

These could then be used in places where non-user-controlled APIs need default functions need to be executed, or as placeholders for default values that may be executed before a function has been supplied. For example:

// third-party API requires a callback or throws, but no functionality
really needs to done:
thirdParty.action(Function.NOOP);

// Function needs a default which *may* execute prior to having a different
one specified (contrived):
const action = (handler = Function.IDENTITY, value = 10) => handler(value);

Thoughts? Seems like something simple with positive value. Thanks!

Eli Perelman

# Isiah Meadows (8 years ago)

I'll note that it's longer than just typing them out manually (and close if they're aliased):

Function.IDENTITY
IDENTITY
x => x

Function.NOOP
NOOP
() => {}

Not sure if it adds anything.

# Tiddo Langerak (8 years ago)

I think this is more about improving readability than saving a few characters.

# Michał Wadas (8 years ago)

Function.prototype is no-op (run Function.prototype.toString() for more information)

# Andy Earnshaw (8 years ago)

On Wed, 10 Aug 2016 at 12:42 Michał Wadas <michalwadas at gmail.com> wrote:

Function.prototype is no-op (run Function.prototype.toString() for more information)

That's true, but using Function.prototype can be confusing when passed to a function as a callback:

someFunc(Function.prototype);

It's not immediately clear that someFunc is expecting a callback function in this case, whereas Function.NOOP or () => {} are pretty obvious.

# Mark S. Miller (8 years ago)

On Wed, Aug 10, 2016 at 4:53 AM, Andy Earnshaw <andyearnshaw at gmail.com>

wrote:

On Wed, 10 Aug 2016 at 12:42 Michał Wadas <michalwadas at gmail.com> wrote:

Function.prototype is no-op (run Function.prototype.toString() for more information)

That's true, but using Function.prototype can be confusing

Indeed.

Function.prototype instanceof Function

false

# Mark S. Miller (8 years ago)

On Wed, Aug 10, 2016 at 2:10 AM, Isiah Meadows <isiahmeadows at gmail.com>

wrote:

I'll note that it's longer than just typing them out manually (and close if they're aliased):

Function.IDENTITY
IDENTITY
x => x

Function.NOOP
NOOP
() => {}

Not sure if it adds anything.

Even aside from brevity, x => x and () => {} are more readable than Function.IDENTITY and Function.NOOP for a very simple reason. The semantics of the shorter forms are obvious and clear, give knowledge only of the core language. The semantics of the named forms can be guessed rather well from the names, but one cannot be sure without looking up or remembering their definitions. As we all know, abstraction has tremendous potential benefits. But it also has these costs -- the need to learn the meaning of new definitions. Only pay these costs when the potential benefits are real.

Also, other things being equal, a briefer form is easier to read. In this case, other things are not equal but both considerations point in the same direction.

# Michał Wadas (8 years ago)

Using instanceof Function can be confusing, because there are objects that are callable but not instanceof Function (document.all, document.createElement('object'), any cross-realm function).

Function.isFunction? :D

# Mark S. Miller (8 years ago)

What's the issue with document.createElement('object')?

# Eli Perelman (8 years ago)

I can understand the sentiment of wanting brevity and avoiding unnecessary abstraction, but in some cases I think it comes at the cost of readability or good practice. This is why variables exist at all: to store commonly used values either for reuse or to cut down on unnecessary allocation.

Sure, I could write code to ensure my numbers did go over a certain limit with Math.min(userInput, 9007199254740991), but readability and abstraction give me something without having to keep this knowledge internally and create my own allocation, e.g. Math.min(userInput, Math.MAX_SAFE_INTEGER.

Now obviously it would be trivial for me to declare these constants in userland code like I already do, e.g. const NOOP = () => {}, but in

projects where it's needed in several files, I'll have to put that in a module or re-declare everywhere. This is not a huge inconvenience but something that could easily allocated for in the language.

The semantics of the named forms can be guessed rather well from the

names, but one cannot be sure without looking up or remembering their definitions.

This is true of anything; you know what you know, and are unsure of what you are unsure of. Those that understand what a no-ops and identity functions are will not need to look it up, and those that do not will look it up until they know it. Just like my personal enemies Array#shift and Array#unshift, I have to look those up every single time, and just because I can't remember which is which or their individual case doesn't mean they don't have value or that I resort to other tricks to avoid their usage. All that to say, I don't think lack of knowledge is a valid argument for these constants' non-inclusion. :)

Only pay these costs when the potential benefits are real.

I think my allusion to potential benefits is avoidance of re-declaration (DRY) and allocation.

Just my thoughts. :)

Eli Perelman

# Andrea Giammarchi (8 years ago)

FWIW, I just use Object most of the time as both no-op and identity (beside primitives, but I rarely have primitives on callback arguments where I need the no-op).

I agree with Mark arrow makes everything ever more explicit and allocation is an engine concern, something that could even not ever happen if it's smart enough to transform no-ops into no-ops and identities into identities.

Just my 2 cents

# Mark S. Miller (8 years ago)

On Wed, Aug 10, 2016 at 7:25 AM, Eli Perelman <eli at eliperelman.com> wrote:

I can understand the sentiment of wanting brevity and avoiding unnecessary abstraction, but in some cases I think it comes at the cost of readability or good practice. This is why variables exist at all: to store commonly used values either for reuse or to cut down on unnecessary allocation.

Sure, I could write code to ensure my numbers did go over a certain limit with Math.min(userInput, 9007199254740991), but readability and abstraction give me something without having to keep this knowledge internally and create my own allocation, e.g. Math.min(userInput, Math.MAX_SAFE_INTEGER.

My message is about tradeoffs -- weighing costs against benefits. The costs I raise are cognitive burden.

x => x has lower cognitive burden than Function.IDENTITY 9007199254740991 has much higher cognitive burden than Math.MAX_SAFE_INTEGER 3.141592653589793 has much higher cognitive burden than Math.PI 3 has lower cognitive burden than Math.THREE

Now obviously it would be trivial for me to declare these constants in userland code like I already do, e.g. const NOOP = () => {}

If there was a reason to do that, then it might make sense to consolidate these. However, this definition looks to me like a userland

const THREE = Math.THREE;

Whether in userland or not, such an "abstraction" only subtracts value.

# Domenic Denicola (8 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Mark S. Miller

What's the issue with document.createElement('object')?

It's a callable exotic object.

On Wed, Aug 10, 2016 at 7:20 AM, Michał Wadas <mailto:michalwadas at gmail.com> wrote: Function.isFunction? :D

typeof is what you are looking for.

# Kris Siegel (8 years ago)

I agree with Mark here plus we have const now which is perfect for something like this if you wanted to take DRY to an extreme.

Ultimately seeing a function declared is obvious, seeing a special key word is not. I have been in software development close to 15 years now and when I first saw this I had to look up to remind myself what identity and noop referred to (used the concepts many times but frequently forget what they're called).

Sometimes DRY can be taken too far. This is one of those times IMO.

# Mark S. Miller (8 years ago)

On Wed, Aug 10, 2016 at 7:33 AM, Mark S. Miller <erights at google.com> wrote:

On Wed, Aug 10, 2016 at 7:25 AM, Eli Perelman <eli at eliperelman.com> wrote:

I can understand the sentiment of wanting brevity and avoiding unnecessary abstraction, but in some cases I think it comes at the cost of readability or good practice. This is why variables exist at all: to store commonly used values either for reuse or to cut down on unnecessary allocation.

Sure, I could write code to ensure my numbers did go over a certain limit with Math.min(userInput, 9007199254740991), but readability and abstraction give me something without having to keep this knowledge internally and create my own allocation, e.g. Math.min(userInput, Math.MAX_SAFE_INTEGER.

My message is about tradeoffs -- weighing costs against benefits. The costs I raise are cognitive burden.

x => x has lower cognitive burden than Function.IDENTITY 9007199254740991 has much higher cognitive burden than Math.MAX_SAFE_INTEGER 3.141592653589793 has much higher cognitive burden than Math.PI 3 has lower cognitive burden than Math.THREE

Now obviously it would be trivial for me to declare these constants in userland code like I already do, e.g. const NOOP = () => {}

If there was a reason to do that, then it might make sense to consolidate these. However, this definition looks to me like a userland

const THREE = Math.THREE;

Meant:

const THREE = 3;

# Peter van der Zee (8 years ago)

What's the issue with document.createElement('object')?

It's a callable exotic object.

Function.isFunction? :D

typeof is what you are looking for.

There is precedent (at least in IE [1]) for exotic functions where typeof returned "unknown". Could happen for any exotic value unless the spec changed on that. An isFunction, or rather, a simple isCallable, may not be that far off the mark and is in line with the existing isArray. Though I'd much rather have callables invariantly locked down to being "typeof function". Even if that means explicit exceptions to some legacy cases.

  • peter

PS. Regexes in firefox were "callable" and had typeof function, but I think that's so far back [2] it's not super relevant here. Of course the same could be said about the IE case.

[1]; one of many examples: stackoverflow.com/questions/10982739/typeof-returning-unknown-in-ie [2]; bugzilla.mozilla.org/show_bug.cgi?id=61911

# Michał Wadas (8 years ago)

Function.isFunction was a joke, I'm pretty sure that only very old legacy code deals with document.all and crazy stuff like that (however, it would be useful, but saving me from typing few more characters isn't something urgent or important).

# Allen Wirfs-Brock (8 years ago)

On Aug 10, 2016, at 8:46 AM, Peter van der Zee <ecma at qfox.nl> wrote:

There is precedent (at least in IE [1]) for exotic functions where typeof returned "unknown". Could happen for any exotic value unless

the spec changed on that<<.

see tc39.github.io/ecma262/#sec-typeof-operator-runtime-semantics-evaluation, tc39.github.io/ecma262/#sec-typeof-operator-runtime-semantics-evaluation

# Kevin Reid (8 years ago)

[no quotes because I'm not replying to anyone in particular]

An advantage that has not been mentioned yet, of having a canonical function instance for particular behaviors, is that it allows for some library-level optimization by being able to know what a function does (which is otherwise opaque). For a simple example:

SomeKindOfImmutableCollection.prototype.map = function (f) { if (f === Function.IDENTITY) { return this; } ...build a new collection with f applied to elements and return it... };

Of course, it would be silly to write coll.map(Function.IDENTITY), but a caller might be passing the function from somewhere else.

(I only intend to point out this one benefit, not to claim it justifies the feature entirely.)

# Andrea Giammarchi (8 years ago)

Kevin that "from somewhere else" has same problems cross-realm instanceof would have, you can't always trust that and if you want to map identities just do it instead of adding extra code ;-)

# Tab Atkins Jr. (8 years ago)

On Wed, Aug 10, 2016 at 7:25 AM, Eli Perelman <eli at eliperelman.com> wrote:

Now obviously it would be trivial for me to declare these constants in userland code like I already do, e.g. const NOOP = () => {}, but in projects where it's needed in several files, I'll have to put that in a module or re-declare everywhere. This is not a huge inconvenience but something that could easily allocated for in the language.

Mark's argument (which I agree with) is that x=>x and ()=>{} are

good spellings of "identity function" and "noop function". It's immediately obvious what they do; obj.doCallback(()=>{}) is about as

easy to understand as obj.doCallback(Function.noop). The ID function is even simpler - obj.map(x=>x) reads extremely well to

me, equal or better than obj.map(Function.id). If we were to reserve "id" and "noop" as bare global variables, I might agree that those were even better, but that's clearly out of the question.

# Alexander Jones (8 years ago)

Those spellings don't help when trying to visually parse a bunch of code which already largely consists of dense punctuation, though, IMO.