What do you call a `function` function?
I've always referenced them as:
Function declarations:
function a() {}
Function expression:
const a = function() {}
Named function expression:
const b = function a() {}
Arrow function:
const a = () => {}
Not sure it's 100% semantic or descriptive, but it's how I've differentiated.
Eli Perelman
"function" function is the best out of all of the alternatives you mentioned.
"Anonymous function declared with the function keyword" if it's not too wordy.
"keyword function" might not be too bad, either...
I just call them "regular functions", "function declarations", or
similar. I'll add "arrow" if there's an arrow, and "async" if there's
an async
keyword.
Isiah Meadows me at isiahmeadows.com
Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com
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 / audience.
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 good.
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 there.
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 accurate.)
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
created.
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 @_ at .
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: gist.github.com/bathos/6bb1e5ef92d2c8f89363c0931d048caf)
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.
Traditional function.
Wow, thanks everyone for all the input!!
I woke up this morning with "classic function" in my head, and immediately
worried about confusing people (particularly those for whom English is not
their native tongue) with class
, since it creates a function (the
constructor).
So I'm currently leaning toward Raul-Sebastian's "traditional function"
(thank you!) since it largely conveys the same meaning as "classic" without
the class
problem.
So taking a page from Darien's magnum opus :-) I get:
- Traditional function (
function
declaration,function
expression) - Constructor (for both
class
andfunction
when the intent is to create a constructor; draw a distinction when necessary) - Method (some vagueness here, we used to use "method" to refer to
traditional functions assigned as object properties, but of course, we got
methods with discernable differences -- can't be constructors, can use
super
-- in ES2015) - Arrow function
async
functionasync
arrow function- Generator function
async
generator function
I think this mostly conveys useful meaning, without getting too pedantic.
Again, thank you everyone for the very useful responses!
-- T.J. Crowder
Bit of a silly one, but begging the list's indulgence:
I routinely explain various JavaScript topics to learners, including arrow functions, method syntax, etc. When I want to contrast "arrow function" (for instance) with functions defined with
function
, it trips me up, and often I end up saying/writing something awful like "function
function". I've considered using "normal function" instead, but arrow functions are normal in today's world, as are functions defined with method syntax (although I'd usually call them methods), so it's...unsatisfying.But
function
function is just so clumsy. And a pedant (none of those here, surely!) could argue the definition (are generatorsfunction
functions? they're defined withfunction
[when you're not using generator method syntax], it just has a*
after it).I've also considered "old-style function," but
function
functions still have a place in today's JavaScript, just not as prominent a place as they used to.A recent post to the list used "conventional function," but it may well have the same problems "normal function" does.
My goal is to be clear, and reasonably accurate, without being overly pedantic.
Any ideas? Should I just stop worrying and learn to love "normal function"? Is there a better term?
Thanks in advance, folks.
-- T.J. Crowder