Returning to an old hobbyhorse: Migrating higher order function names to comply with API guidelines

I’m not relitigating anything, I’m saying your statement is incorrect.

1 Like

It's a form version of filter. "Well-actually"ing that isn't constructive.

1 Like

It does the exact opposite of filter.

The Swift standard library does not currently have an in-place version of filter. That’s just a fact.

Saying “Well actually, it has something that can be twisted into doing the same thing as filter by negating the predicate” isn’t constructive.

That fact that there is another way of writing the same thing is not relevant. I can write a for loop that does the same thing.

But no one would say “The in-place version of filter is called a for loop”.

4 Likes

We discussed both the polarity of the predicate and the merits of having both polarities as separate functions during SE-0197. The standard library has a form version of filter already, and proposing a second one to match filter's polarity is something already discussed and rejected in a previous review.

1 Like

That is already a method on MutableCollection.

1 Like

There is more than one kind of learner. I teach Swift more often to people with existing programming experience, and it’s so easy to lose someone’s interest when Swift is seen as having different names for want of a nail.

This converse can also apply for an actual beginner. Terms of art help a programmer who started with Swift know what to look for when they branch out to other languages. No matter how stringently Swift applies its guidelines to the stdlib, beginners will bootstrap their awareness of Swift APIs through memorization and pattern matching a lot more than they are going to deeply ingest the metaphors behind “mapping” or “selecting”. We should avoid doing them the disservice of feeling like they have to start all over again with a different language.

Swift has to fight the perception that it is just some silly Apple language that Apple forces you to learn, just as much as it needs to stick to the path it has charted for itself. The API guidelines are fabulous, but don’t exist in a vacuum.

21 Likes

We should focus on 'training" swift's code completion to suggest a new symbol to take precedence over an imported discouraged symbol when importing a new package.
There are a couple of issues that would need to be addressed:

  1. How do we "train" the code completion logic to pick an implementation over another?
  2. Raise the precedence of some symbols in overloading resolution implicitly via the same mechanism so that when somebody calls a symbol with that name then it gets picked up by a "shim" library that was imported.
  3. We still need a way to disambiguate overloads on the call site (out of scope)

Intellisence allows custom training that could function in a similar way as the bellow suggestions but instead of arbitrary being able to train code completion we would sort of "tag" our preference using an attributed.

We can address 1 and 2 with an attribute that will inform the overload resolution and code completion that should take precedence:
@symbolPrecedence(overloadRename: "mapping(_:)", higherThan: Swift , lowerThan: OtherLib)

  • overloadRename: The name of an method similar to the rename property
  • higherThan, lowerThan : a library name that would function in a similar way as "Precedence Group Declaration" . Hopefully this can be strongly typed in some form via import

Imagine I wanted to create new package that contains all the new suggested name changes, lets call this package SwiftBoost.

\\ SwiftBoost Library

import Swift
import OtherLib


extension CollectionOrArrayEtc {
...
@available(swift 5.3)
@symbolPrecedence(overloadRename: "mapping(_:)", higherThan: Swift , lowerThan: OtherLib)
@inlinable public func mapping<T>(_ transform: (Character) throws -> T) rethrows -> [T] { ... }

@symbolPrecedence higherThan: Swift , lowerThan: OtherLib)
@available(*, deprecated: 5.3, renamed: "mapping(_:)", message: "Please use mapping")
@inlinable public func map<T>(_ transform: (Character) throws -> T) rethrows -> [T] { ... }

...
}
// New Lib ABC
import SwiftBoost

var someArray = ...

someArray.mapping(...)
In ABC when I start typing map, the symbol mapping should be the first choice on code completion.

some.map(...)
In ABC if I were to choose map anyway then I will get a warning that is coming from SwiftBoost.

some.Swift::map(...)
some.SwiftBoost::map(...)

It would be really helpful if we could disambiguate which package the symbol is being picked up.

Reference:

https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID380

Hey Erica,

Thank you so much for pushing on this. I would recommend separating the two issues:

  1. A proposal to extend availability to support "discouraging" APIs is a generally useful thing, either because they are not generally useful, the are obsolete but cannot be removed from ABI compatibility reasons, etc. I think we'd have to have a discussion about what discouraging means - clearly shouldn't show up in code completion, should it also be dropped from documentation, or just marked with a "discouraged" tag?

  2. should we rename map/filter/reduce? As you say, this will be controversial. I think it is mostly completely off the table without #1 happening, but even with #1 happening, it is still debatable (I was argue in favor of the rename FWIW, even though I originally argued against renaming them in the Swift 3 days).

If you agree with that characterization, then I think it makes sense to start with #1 and get it done. This is a modest proposal and a pretty simple change to the compiler. I suspect that someone would be willing to help prototype the compiler change, and if not, I could probably help in a few weeks.

-Chris

4 Likes

I’m for it.

It seems pretty simple to change my source code to be current, and also simple to not change my source code to be stubborn.

(Also it seems easy for Xcode / other tools to have a one-button “fix this codebase” to update away from discouraged terms, since assumedly they’d always be a simple rename?)

-Wil

2 Likes

When I google 'map filter reduce swift' Mr Google gives me over 3 million responses. It also suggests the same searches for javascript, python, java, c++. Are we going to change all those web sites and those other languages too? It's a rite of passage to read a blog or three about swift and map filter reduce. It's probably also a rite of passage to write one.

These method names have a long history as terms of art in other languages and have a five year history in Swift. I haven't heard any good reasons why they should be changed.

Somebody said yesterday that we should focus on actual improvements to the language so we can do more with it, not mess around renaming stuff. I agree with that.

11 Likes

Let's pick this up after dubs.

I program with so many different languages nowadays but I want Swift to win.

map, filter, etc. are everywhere, do you want to be perfect or do you want to be accessible to other users from other languages?

I don’t know what will make Swift win, but this seems like a decision that influences it to me. If it was my language I would pick the spellings people are familiar with over perfectionism.

9 Likes

I suppose there is no hope of just… adding the methods desired by this proposal and keeping the term of art names? That, to me, seems like the way for everything to be fine. I'm not a huge fan of synonymous methods/functions but this seems like a reasonable case. it is useful for map, filter, reduce and friends to appear with the common names. Those names also don't fit the guidelines terribly well. If we add synonyms, whenever someone learning swift is bothered by the misfit names, you can explain that there are alternatives to address exactly that issue.

If we'd introduce these new function names, could we still back deploy them to older platforms with things like @_alwaysEmitIntoClient or some other internal compiler attributes? I'm fine with updating the Swift version, but it does not mean I can update my deployment target, but I still want to benefit from the introduced consistency.

1 Like

One option here would be to introduce a way of aliasing functions. Ruby has this sort of feature: How to Use The Ruby Alias Keyword - RubyGuides

Swift already has typealias how about “funcalias”?

1 Like

Personally I believe that these specific examples are terms of art enough to not warrant changing.
There is a related topic to it though, which is not discussed here but influences it, over the fact that we have to basically duplicate methods for mutating and nonmutating behaviours and deal with the naming differences between those. Afaik there were a couple of discussions on trying to solve that issue though, and nothing came out...

1 Like

select and reject in favor of filter method please. Such clarity in reading over negations or complex boolean expression...

3 Likes

This makes me wonder if maybe our attributes should be making the semantic differences explicit here... In both cases, map doesn't show up in code completion any more , but why it doesn't show up is different between something that's been renamed vs something that just isn't intended to be explicitly written at all (such as the literal convertible initializer). Would it be better to separate attributes into cause (@renamed) and effect (@dontShowInCodeCompletion)? It seems like it'd simplify the compiler since, in this example, the autocomplete engine would only have one attribute, @dontShowInCodeCompletion, to check for. Likewise, the fixit engine doesn't need to know whether something will show up in code completion to check if it's been renamed.

I couldn't agree more :100:. The same argument also applies to filter and reduce, although they are less used than map.

Terms of art are important, otherwise they wouldn't be terms of art. Their meaning is important. It's the very reason that the the old "incorrect" version of flatMap was renamed to compactMap, because flatMap has a well defined meaning and it was confusing to use that name for something else.

If map were renamed, would Publishers.Map also change? Ditto for Publishers.Filter and Publishers.Reduce. These names are everywhere, and for a good reason. Changing them now would be, in my view, hugely detrimental to the Swift language and, frankly, only give ammunition to the view that the Swift Evolution process is just a bike-shedding party.

10 Likes

Although I understand the arguments in favor of a change, I think having both map() and mapping() is the worst possible choice because then it appears that one is a mutating method and one is not.

map() without mapping() is a term of art. map() with mapping() is, IMO confusing.

4 Likes