[swift-evolution] Stdlib closure argument labels and parameter names
Dave Abrahams
dabrahams at apple.com
Sat Jun 25 17:34:46 CDT 2016
on Wed Jun 22 2016, David Hart <david-AT-hartbit.com> wrote:
> I did not pay a lot of attention to this renaming because I thought
> the trailing closure syntax would shield me from labels. But I forgot
> that in if/for/while statements, I’m going to be forced to use
> them. In those cases, I’d prefer more succinct labels.
>
> I added some comments inline where I have issues with the new labels:
>
>> On 23 Jun 2016, at 04:02, Erica Sadun via swift-evolution <swift-evolution at swift.org> wrote:
>>
>> On Jun 20, 2016, at 3:25 PM, Dave Abrahams via swift-evolution
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>
>> wrote:
>>>
>>>
>>> Hi All,
>>>
>>> A couple of weeks ago we started to notice that we had some poorly-named
>>> closure parameters and argument labels in the standard library, so we
>>> did a complete audit of the standard library's APIs and came up with a
>>> preliminary proposal for changes, which we applied in a branch and you
>>> can review in https://github.com/apple/swift/pull/2981 <https://github.com/apple/swift/pull/2981>. Let's please
>>> carry on further discussion here rather than in the pull request, though.
>>
>> - /// - `isEquivalent(a, a)` is always `true`. (Reflexivity)
>> - /// - `isEquivalent(a, b)` implies `isEquivalent(b, a)`. (Symmetry)
>> - /// - If `isEquivalent(a, b)` and `isEquivalent(b, c)` are both `true`, then
>> - /// `isEquivalent(a, c)` is also `true`. (Transitivity)
>> + /// - `areEquivalent(a, a)` is always `true`. (Reflexivity)
>> + /// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry)
>> + /// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then
>> + /// `areEquivalent(a, c)` is also `true`. (Transitivity)
>>
>> I like this change!
>>
>> - func forEach<S: SequenceType>(_ body: (S.Iterator.Element) -> ())
>> + func forEach<S: SequenceType>(invoke body: (S.Iterator.Element) -> ())
>>
>> Adding an external label makes sense here. This is a procedural call and
>> using it within the parens should have a "code ripple".
>>
>> That said, would prefer `do` or `perform` over `invoke` or `invoking` as in
>> `runRaceTest`, `_forAllPermutationsImpl`, `expectFailure`, etc. This also applies
>> where there's a `body` label instead of an empty external label.
>>
>> -public func withInvalidOrderings(_ body: ((Int, Int) -> Bool) -> Void) {
>> +public func withInvalidOrderings(invoke body: ((Int, Int) -> Bool) -> Void) {
>>
>> For any with/external label pair, I'd prefer `with-do` or `with-perform`
>> over `with-invoke`.
>>
>> - return IteratorSequence(it).reduce(initial, combine: f)
>> + return IteratorSequence(it).reduce(initial, accumulatingBy: f)
>>
>> For `reduce`, I'd prefer `applying:` or `byApplying:`
>>
>> Similarly in `starts(with:comparingBy:)`, I'd prefer byComparing`,
>> min/max, byOrdering
>>
>> - ).encode(encoding, output: output)
>> + ).encode(encoding, sendingOutputTo: processCodeUnit)
>>
>> How about `exportingTo`?
>
> I know this label does not need to be necessarily short, but I’d
> prefer Erica’s suggestion, or even `to`. encode(a, to: b) reads well
> to me.
Makes sense.
>> - tempwords.sort(isOrderedBefore: <)
>> + tempwords.sort(orderingBy: <)
>>
>> With `sort` and `sorted`, I'd prefer `by:`
>
> I also agree that `by` is more than enough. `sort` already implies an
> `ordering`.
See my reply to Erica.
>> - if !expected.elementsEqual(actual, isEquivalent: sameValue) {
>> + if !expected.elementsEqual(actual, comparingBy: sameValue) {
>>
>> I'm torn on this one. I don't like but I don't have a good solution.
>
> This one has a noticeable chance of appearing in conditional clauses
> so I’d like to go for something short. How about `by`?
You know, it's not bad. Maybe it's just the fever talking but I am
starting to like these short labels.
>> - /// for which `predicate(x) == true`.
>> + /// for which `isIncluded(x) == true`.
>> - _base: base.makeIterator(), whereElementsSatisfy: _include)
>> + _base: base.makeIterator(), suchThat: _include)
>>
>> How about simply `include` for both? I get the `is` desire but it's being tossed away
>> in a lot of other places in this diff. and `suchThat` feels out of place.
>>
>> - || u16.contains({ $0 > 127 || _isspace_clocale($0) }) {
>> + || u16.contains(elementWhere: { $0 > 127 || _isspace_clocale($0) }) {
>>
>> I assume the challenge here is differentiating contains(element) from contains(closure).
>> This feels predicate-y, which is why I put it near the predicates. But I think something
>> like `containsElement(where:)` works better.
>
> Agreed. Again, same argument. As this is a boolean function, this has
> a higher chance of appearing in conditional clauses so should be
> short. `where` works well for me.
if friends.contains(where: {$0.firstName == "ted"}) { ... }
? `contains(where:` doesn't seem very fluent to me.
>> - let result = try base._withUnsafeMutableBufferPointerIfSupported(body)
>> + let result = try base._withUnsafeMutableBufferPointerIfSupported(invoke: body)
>>
>> I hate "ifSupported" but that's another discussion (withSupportedUnsafeMutableBufferPointer,
>> withAvailableUnsafeMutableBufferPointer, it's all lipstick)
>>
>> This is procedural, so `do` or `perform` rather than `invoke`
>>
>> - for test in removeFirstTests.filter({ $0.numberToRemove == 1 }) {
>> + for test in removeFirstTests.filter(
>> + suchThat: { $0.numberToRemove == 1 }
>>
>> The difference between `filter` and `forEach` is that `forEach` is explicitly
>> procedural while `filter` is functional. I do not like functional chainable
>> calls being modified to use explicit external labels in this way.
>>
>> I'd prefer no label here.
>
> Quick aside. Eric, if you prefer no label here, why did you not also
> prefer no labels for contains and elementsEqual? They also have very
> functional. But if we must have a label, I’d vote for `where`.
IMO `where` fails to correct one of the great weaknesses of the basename
`filter`, which is the strong implication that the predicate's meaning
is the inverse of what it actually is. That's why I favor `soEach`.
>> public func split(
>> maxSplits: Int = Int.max,
>> omittingEmptySubsequences: Bool = true,
>> - isSeparator: @noescape (Base.Iterator.Element) throws -> Bool
>> + separatedWhere isSeparator: @noescape (Base.Iterator.Element) throws -> Bool
>>
>> I'm torn on this one. It's not the worst ever but something more like where/at/when
>> makes more sense to me than "separatedWhere/separatedAt/separatedWhen”.
>
> I care less about this one, but `where` also sounds better.
As I noted in my reply to Erica, I think `where` without some reference
to the separator is potentially misleading.
Perhaps
contiguousNonPrimes = (0..<100).split(whereSeparator: isPrime)
would be better for use sites than what's in the patch, though.
--
-Dave
More information about the swift-evolution
mailing list