[swift-evolution] [Draft] Apply -ed/-ing rule to core functional methods (e.g. filter => filtered)
Brent Royal-Gordon
brent at architechies.com
Thu Jun 16 17:21:50 CDT 2016
> map => mapped
> flatMap => flatMapped
> filter => filtered
> reduce => reduced
You posted before I finished responding to this on the other thread, so I guess I'll do it here.
You're right that renaming these operations wouldn't be terrible. But I think they're easily distinguishable from things like `dropFirst`, and distinguishable in a way that tilts rather strongly towards leaving these as-is.
`map`, `filter`, and `reduce` are *the* higher-order functions. Almost anything with any kind of block/lambda/closure feature supports them (I'm giving the side-eye to Foundation here), and all three names are backed by *very* strong conventions:
* `map` is by far the strongest. It is universally supported among languages with higher-order collection operations, and is almost always called `map`. In Wikipedia's list of 32 languages with a `map` <https://en.wikipedia.org/wiki/Map_(higher-order_function)#Language_comparison>, we find (grouping together similar names like `map` and `maplist`, and double-counting languages with several aliases):
Map: 19
Collect: 3
Apply: 3
Special syntax: 2
Select: 1 (C#, which uses it in the SQL-inspired LINQ)
Transform: 1 (C++, which uses a bizarre signature involving an out parameter)
ForEach: 1 (XPath)
* `filter` is supported nearly as widely as `map`, and the name `filter` is used nearly as consistently as `map`. Wikipedia lists 27 languages supporting a `filter`-style function, and `filter` is by far the most common choice, arguably favored even more consistently than `map` <https://en.wikipedia.org/wiki/Filter_(higher-order_function)>:
Filter: 17
Select: 4
Special syntax: 3
FilteredArrayUsingPredicate: 1 (Foundation, doesn't actually take a closure)
Where: 1 (C#, in LINQ)
CopyIf: 1 (C++, bizarre signature)
FindAll: 1
Grep: 1
RemoveIfNot: 1
* `reduce` is extremely popular among functional languages because it's a primitive list-handling operation, although it's a little less common among mainstream languages than the other two. It *does* actually have an alternate name, `fold`, which is nearly as common as `reduce`. However, languages using `fold` are usually those which support both leftward- and rightward-reducing versions of the operation, whereas languages using `reduce` usually don't. Swift falls into the second camp. From Wikipedia's 39-language list <https://en.wikipedia.org/wiki/Fold_(higher-order_function)>:
Reduce: 20 (with both left and right: 4)
Fold: 18 (with both left and right: 12)
Inject: 3
Special syntax: 3
Aggregate: 1 (C#, in LINQ)
Accumulate: 1 (C++, bizarre signature)
Partition: 1
ToIterator: 1
(Note that, although I *would* have counted the -ed or -ing forms of these names with the originals, I don't believe I saw any.)
Another illustration of the strength of these names: Google named a distributed computing project MapReduce, and everyone basically understood what it meant.
If `map`, `filter`, and `reduce` are not covered by the term-of-art rule, we might as well rename it to the sin()-and-Int rule, because I don't know what else it would cover. There is remarkable consistency in the naming of these operations across dozens of languages.
(Incidentally, I think if we *do* decide to rename the main higher-order list APIs, they would be better named as `mapping(to:)`, `flatMapping(to:)`, `filtering(by:)`, and `reducing(from:with:)`. Unlike `sorted`, the parameters are mandatory and deeply necessary to understand what the call does, so they deserve grammatical labels. Grammatical labels usually imply -ing form, not -ed form. If we want to address Dave's complaint about the ambiguity of `filter`, we might rename that to `selecting(where:)`, which is crystal clear about whether it keeps the true elements or the false ones.)
> dropFirst => droppingFirst
> dropLast => droppingLast
Here, however, I think you're being under-ambitious.
* Even with a nonmutating suffix, `dropping` carries such a strong connotation of deletion that I think it's a poor choice. I would prefer `skipping`, which is much more clearly an operation that doesn't change anything (and finds precedent in other languages—`skip*` functions are a common alternative name for `drop*` functions.)
* The `First` and `Last` suffixes are inappropriate for the versions of this function which take an argument. These are inverse operations to `prefix(_:)` and `suffix(_:)`, and should be named accordingly.
* `prefix(upTo:)`, `prefix(through:)`, and `suffix(from:)` are sort of strange, because they're tied deeply into indices and aren't closely related to the other prefix/suffix methods. I would at least seriously consider renaming these to `upTo(_:)`, `through(_:)`, and `from(_:)`—or perhaps even reimagine these entirely as subscripts `[upTo:]`, `[through:]`, and `[from:]`.
This is the area where the term-of-art argument is weaker, the API is more inconsistent and difficult to understand, and the methods are more rarely seen and so more important to be understandable without remembering them. All of these factors combine to make the gains from rationalizing them greater.
`map`, `flatMap`, `filter`, and `reduce` are in some ways the "sexy" targets: We use them all over the place, and changing them would "fix" more lines of code. But these calls are so common, and so heavily precedented, that few users will be confused by them—they've probably seen them before Swift, and even if they haven't, they'll see them enough to keep their meanings fresh in their minds. It is the dimmer, less loved corners of the standard library which we ought to focus on, because they stand to benefit much more from following the conventions than the things we use constantly.
--
Brent Royal-Gordon
Architechies
More information about the swift-evolution
mailing list