I usually say "function function," but yeah ... it’s painful haha. I
sometimes work with devs whose experience with ES functions is limited to
methods and arrow functions, so "normal function" doesn’t really fly
anymore. I’ve considered "classic function," but that probably wouldn’t
help much.

I have no good answer to the root question here, but I’ve thought about
this a bit. There seem to be many ways we could categorize and name "types
of functions" in ES and the best choices probably vary with context /

# 1. Naming after syntactic productions

The ES spec includes the following relevant named syntactic productions. So
the nice thing about using a taxonomy based on syntax is that it minimizes
subjective factors and matches up fairly well with how many people think
about code:

- ArrowFunction
- AsyncArrowFunction
- AsyncFunctionDeclaration
- AsyncFunctionExpression
- AsyncGeneratorDeclaration
- AsyncGeneratorExpression
- ClassDeclaration
- ClassExpression
- FunctionDeclaration
- FunctionExpression
- GeneratorDeclaration
- GeneratorExpression
- MethodDefinition

The ClassDeclaration and ClassExpression ones are a bit fuzzy, since an
_explicit_ constructor is a MethodDefinition syntactically, but
ClassDeclarations and ClassExpressions without explicit constructor
MethodDeclarations do still create functions. In any case, the fact that
`constructor` is a MethodDeclaration is an example of a shortcoming of this
approach: a constructor is specifically _not_ usable as a method in the
sense that we usually mean, so you really feel the "these are just names
for the grammar" factor there.

Another concern might be that syntax is not the only means by which
functions are created, so it we don’t get a corresponding name for
everything. Use of the four Function constructors, bound function exotic
objects, and Proxies are also avenues by which new callable/constructable
objects can be created, but they have no syntactic expression, so they
might be awkward to categorize along these lines.

In this taxonomy, the name would be "function declaration" or "function
expression". That doesn’t actually address the ambiguity problem around
unless everybody is on the same page, so I think this angle is not very

# 2. Functions by class

All function objects (or at least all non-"extrinsic" ones) will report
themselves as instances of one of these four corresponding constructors
from their Realm:

- %AsyncFunction% (`(async function() {}).constructor`)
- %AsyncGeneratorFunction% (`(async function * () {}).constructor`)
- %Function% (`Function`)
- %GeneratorFunction% (`(function * () {}).constructor`)

In this taxonomy, we get to place every (or very nearly every) function
into a category without trouble. On one hand, these are the most dramatic
"function type" distinctions we can draw, so it isn’t-not-useful — but it
doesn’t give us a way to talk about distinctions between functions created
with class syntax, arrow syntax, etc, so it’s also a nonstarter.

# 3. What constitutes a type of function

I’m figuring that underlying the communication problem is a different
issue: what constitutes a "type of function" in ES may not really have a
simple and objective answer.

For example, from a syntactic POV, FunctionDeclaration and
FunctionExpression are different in several ways, like how their names may
be derived, what/whether bindings are created and in what scope, and
whether hoisting takes place. But looked at as values (that is, considering
just the function objects that the syntax creates), these two forms end up
exhibiting no observable behavioral differences. Given that some functions
do possess contrasting semantics that are _very_ observable, like async
functions, can we really say a FunctionDeclaration and FunctionExpression
are different "types" on the same order?

I think the best answer is no — they are, instead, different syntactic
forms for describing what should be considered the same type of function.
So let’s look at what behaviors of the functions themselves, rather than
the syntax used, really can differ, because maybe there’s a better answer

# 4. Functions by semantics

The distinction between arrow functions and non-arrow functions (of various
types) is that their [[ThisMode]] is "lexical" and the others are not — but
the there are actually two other [[ThisMode]] values, "global" and
"strict". It is not simply a binary distinction between lexical and
non-lexical "this" because functions created in sloppy mode may resolve
"this" to the global object and functions created in strict mode may not.

(Bear with me alright ^_^;)

Related to (but not directly corresponding to) the "functions by class"
section, there is a [[FunctionKind]] slot. Its value may be "normal",
"classConstructor", "generator", or "async". During function allocation,
other similar values are transiently in the mix, like "non-constructor"
(which prevents the allowance of a [[Construct]] method for an otherwise
"normal" function), and "async generator" (which becomes "generator");
during function initialization there are also "Arrow", "Normal" and
"Method". But I mention these only to clarify that they are used to set
other properties (e.g. Arrow leads to setting [[ThisMode]] to "lexical") —
they don’t remain "part" of the function like [[FunctionKind]] does.

So both the "classConstructor" and "normal" kinds correspond to %Function%,
while both %AsyncGeneratorFunction% and %GeneratorFunction% correspond to
"generator". Whew.

(I’ve probably got some details wrong there, but I think it’s _mostly_

Two fundamental forms correspond to _constructable_ functions:

- `class Foo {}`, `(class {})`
- `function Foo() {}`, `(function() {})`, `new Function()`

These types of functions obtain a `prototype` property when they are

In addition a bound function exotic object will be constructable if the
function it proxies is constructable.

All other functions are not constructable — this is the distinction between
`function foo() {}` and the method `foo() {}` (it’s not sugar). But async
functions, generators, and async generators are never constructable in any
of their syntactic forms — declarations, expressions/arrows, methods — so
for those, method syntax _is_ just sugar — or rather would be, if not for
one last key slot, [[HomeObject]], which exists only for methods and
affords them the ability to use super expressions. You know ... writing it
all out like this makes it suddenly feel _very_ complicated @_@.

As for [[Call]], all functions possess this internal method, even classes;
however, if the [[FunctionKind]] is "classConstructor", then an attempt to
use [[Call]] will always throw, making it _effectively_ unavailable. What
this means in practice is that the unique property of `function` functions
is that they are the only functions which can be both called and
constructed (not counting black magic that makes other functions _seem_ to
have both capabilities).

Putting [[HomeObject]] (which doesn’t seem like a "type" issue so much) and
bound function exotic objects (which are more or less proxies to other
functions) aside, we could say there are three abstract semantic attributes
that contribute significantly to what we as language users typically think
of as "function type":

- presence of [[Construct]]
- value of [[FunctionKind]]
- value of [[ThisMode]]

At the loss of some precision, we can conflate "global" and "strict"
[[ThisMode]] since, admittedly, we don’t often think about this and they
don’t correspond to any specific syntax. What we get in this taxonomic
model then is a matrix with three dimensions. The first column is
[[FunctionKind]], the second is whether [[Construct]] exists (the extra
axis), and then horizontally we have [[ThisMode]]. Cells with "-" describe
combinations that aren’t possible, while the others provide
(non-exhaustive) examples of corresponding syntax.

(I’m not sure if ES discuss will let me include a GFM table, and I imagine
some people look at this via email, so here’s an alternative link:

| FK               | Cstr | TM: Lexical    | TM: Global / Strict
| normal           | cstr | -              | function() {}; new Function
| normal           | no   | () => {}       | x() {}
| classConstructor | cstr | -              | class {}
| classConstructor | no   | -              | -
| generator        | cstr | -              | -
| generator        | no   | -              | function * () {}; * x() {}
| async            | cstr | -              | -
| async            | no   | async () => {} | async function() {}; async x()
{} |

If we do turn the non-empty cells into a list, we get seven types of
functions. If we label them by the primary facets that make each
_semantically unique,_ we end up with a final taxonomy that would look
something like this:

- lexical function
- async lexical function
- callable constructor
- method
- constructor
- generator / generator method
- async function / async method

So circling back to the original question, I got "callable constructor"!
(record scratch) Eh...

Okay, I guess that isn’t gonna catch on haha. It may more accurately
reflect what makes `function`-keyword-functions unique, but given common
usage patterns, it would be too confusing. Inversion could help a little
("constructable function"), but not enough.

Anyway, it’s just a little investigation. Nobody’s gonna start using these
terms. But it was interesting to think about and you’re not alone in
sometimes struggling to find the best terminology for different kinds of
functions that ES developers with diverse experience levels all find clear.
es-discuss mailing list

Reply via email to