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 @_@. 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: https://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.
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss