[swift-evolution] Stdlib closure argument labels and parameter names
Karl
razielim at gmail.com
Sun Jun 26 02:31:47 CDT 2016
> On 26 Jun 2016, at 02:03, Erica Sadun via swift-evolution <swift-evolution at swift.org> wrote:
>
> On Jun 25, 2016, at 4:25 PM, Dave Abrahams <dabrahams at apple.com <mailto:dabrahams at apple.com>> wrote:
>> on Wed Jun 22 2016, Erica Sadun <erica-AT-ericasadun.com <http://erica-at-ericasadun.com/>> 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:
>>>
>>> - 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".
>>
>> I don't think I understand what you mean here.
>
> When using a procedural "trailable" closure inside parentheses, the intention
> to do so should be clear:
>
> p(x, perform: {...})
> p(x, do: {...})
>
> vs
>
> p(x) {
> ...
> }
>
> Anyone reading this code can immediately identify that an otherwise trailing
> closure has been pulled inside the signature because the call has become
> significantly more complex. The point-of-coding decision ripples through
> to point-of-reading/point-of-maintenance.
>
>>> That said, would prefer `do` or `perform` over `invoke` or `invoking` as in
>>> `runRaceTest`, `_forAllPermutationsImpl`, `expectFailure`, etc.
>>
>> Sorry, again, I'm a little lost. Forgive my overly-literal brain but
>> could you please spell out how those latter 3 names relate to the choice
>> of `do` or `perform` over `invoke`?
>
> I read through the pull request. I grouped related modifications
> together, although not exhaustively. They fall under the same umbrella,
> choosing `do` or `perform` compared to `invoke` or `invoking`.
>
> - _forAllPermutationsImpl(index + 1, size, &perm, &visited, body)
> + _forAllPermutationsImpl(index + 1, size, &perm, &visited, invoke: body)
>
> -public func runRaceTest(trials: Int, threads: Int? = nil, body: () -> ()) {
> +public func runRaceTest(
> + trials: Int, threads: Int? = nil, invoking body: () -> ()
> +) {
>
> -public func expectFailure(${TRACE}, body: () -> Void) {
> +public func expectFailure(${TRACE}, invoking body: () -> Void) {
>
>
>>
>>> This also applies where there's a `body` label instead of an empty
>>> external label.
>>
>> We don't have any methods with a `body` label IIUC.
>
> You did, as in the examples just above, which you recommend a rename to `invoke` or
> `invoking`.
>
>>> -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`.
>>
>> OK. Is there a rationale, or is it just personal taste?
>
> Strong personal taste, backed by tech writing. Simpler words.
> From the Corpus of Contemporary American English:
> do: 18
> perform: 955
> invoke: does not appear on list.
>>
>>> - return IteratorSequence(it).reduce(initial, combine: f)
>>> + return IteratorSequence(it).reduce(initial, accumulatingBy: f)
>>>
>>> For `reduce`, I'd prefer `applying:` or `byApplying:`
>>
>> Makes sense.
>>
>>> Similarly in `starts(with:comparingBy:)`, I'd prefer byComparing`,
>>
>> I don't see how that works. You're not comparing the closure with
>> anything.
>
> I get your point but I think it's unnecessarily fussy.
>
> Alternatives are slim on the ground. `usingComparison:` is too long,
> as is `byComparingWith:` (which still reads better but you will point
> out can be mistaken by some pedant to mean that the sequence is being
> compared to the closure), and you won't allow for `comparing:`.
> I'm not in love with `comparingWith:` but it reads slightly better to me
> than `comparingBy:`.
>
>
>>
>>> min/max, byOrdering
>>
>> Likewise, you're not ordering the closure.
>
> Same reasoning.
>
>>
>>> - ).encode(encoding, output: output)
>>> + ).encode(encoding, sendingOutputTo: processCodeUnit)
>>>
>>> How about `exportingTo`?
>>
>> “export” is freighted with various connotations that I don't think we
>> want to make here. In fact it doesn't mean anything more exotic than
>> “sending output to.”
>
> For a language that treasures concision and clarity, this may be clear
> but it's notably inconcise. (Yes, that is a new word.)
verbose
>
>>
>>> - tempwords.sort(isOrderedBefore: <)
>>> + tempwords.sort(orderingBy: <)
>>>
>>> With `sort` and `sorted`, I'd prefer `by:`
>>
>> When people talk about “sorting by XXX”, XXX is a property of the
>> elements being sorted. Therefore IMIO that label should probably be
>> reserved for a version that takes a unary projection function:
>>
>> friends.sort(by: {$0.lastName})
>
> But they're sorting now, and that feature isn't in Swift.
>
> However, `sorted` falls under another umbrella, which is potential
> chaining. In such case, I would prefer there be no internal label at all
> to allow cleaner functional flow.
>
>>
>>> - 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.
>
> I actually meant to move this up to the other discussion of `byComparing` and
> forgot to. So please assume you disagree with me on this one too.
>
>>>
>>> - /// 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?
>>
>> Looking at the effect on the doc comments—which is how we should judge
>> parameter names—I think `predicate` might read better.
>
> I like `predicate`. I endorse `predicate`. Does this mean the rule of "must
> read like a sentence" can be overturned for things like "comparison" and
> "order"? If so, woo!
>
>>
>>> 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.
>>
>> I think I'm pretty strongly sold on “soEach” as a label for closures in
>> all the filtering components.
>
> To quote greatness:, "I personally find `soEach` repulsive".
> (cite: http://article.gmane.org/gmane.comp.lang.swift.evolution/16998/match=repulsive <http://article.gmane.org/gmane.comp.lang.swift.evolution/16998/match=repulsive>)
>
>>
>>> - || 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.
>>
>> I understand your motivation for suggesting it. The downside is that it
>> severs the strong basename relationship between algorithms that do
>> effectively the same things, one using a default comparison and the
>> other using an explicitly-specified one. I'm truly not sure where we
>> ought to land on this one.
>
> May I recommend `predicate:` then since it looks like that's actually a possibility?
>
>>
>>> - let result = try base._withUnsafeMutableBufferPointerIfSupported(body)
>>> + let result = try base._withUnsafeMutableBufferPointerIfSupported(invoke: body)
>>>
>>> I hate "ifSupported"
>>
>> Me too.
>>
>>> but that's another discussion
>>
>> Quite. It's also an internal API so it's not an evolution issue. The
>> point of having that change as part of the diff (as with all the other
>> use sites), is to observe how the changes affect real usage.
>
> Woody Allen: "The heart wants what it wants"
> Me: "The spleen vents what it vents"
>
>>
>>> (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.
>>
>> Can you provide rationale for treating functional methods differently,
>> or is it just personal taste?
>
> Functional programming flow. I follow Kevin Ballard's rule of parens around
> functional elements and naked braces for trailing closures that do not return
> values. This ensures the compiler is never confused at places like:
>
> for x in foo when y.f{...} {
> ...
> }
>
> and instantly identifies to the reader when there's a non-returning scope, as
> in forEach or GCD dispatch.
>
> However, if you use chaining and if the language insists on external label
> preambles, which would not be seen when using the call with a trailing closure,
> it becomes far less readable and encourages people to use trailing closures to
> avoid the label, i.e. an attractive nuisance. Simple selectors encourage better fp:
>
> let x = myStream.f1({...}).f2({...})
>
>>
>>> 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".
>>
>> The biggest reason to keep “separate” in the name is the connection with
>> the semantics of the other `split` method, that takes a single element
>> that is tested for equality. I think this is very similar to the
>> `contains(elementWhere` vs `containsElement(where` discussion. If you
>> leave “separate” out of the name it fails to imply that those elements
>> for which the predicate returns true are not present in the result.
>
> `predicate` ftw.
>
>>
>>>
>>> + count: __manager._headerPointer.pointee.count)
>>>
>>> For the sake of Zippy the Pinhead, surely there has to be something better than `pointee`.
>>> Like...`reference`?
>>
>> It's not a reference; it's a value. But that, my friend, is an
>> *entirely* different discussion. Let's try to stick to the scope of the
>> proposal: names and labels for parameters of function type, OK?
>
> It was humor. It was at the end. I assumed the joke would lighten the
> previous complaints and bookend the positive support at the start of
> my message.
>
>>
>> --
>> -Dave
>
> -- E
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
The problem with finding a good argument label for “filter” is that when you use it as a verb in regular English, you typically mean to filter something _out_, but in this usage, we mean a specifier for the items to include.
If we flipped it (that’s another proposal, I know), we could have the eloquent-looking:
[1, 2, 3, 4, 5].filter(out: { $0 > 3 }) // Returns [1, 2, 3]
Otherwise, the only context I can think of where you regularly talk about inclusive filters is in electronics, where we have low-pass/high-pass filters. Given that:
[1, 2, 3, 4, 5].filter(passing: { $0 % 2 == 0 }) // Returns [2, 4]
I’m not sure about it, but I definitely think it should be “passing” or “including” or some other very descriptive word that indicates the closure is a pass condition. “filter(where: {})” could be either an inclusive or exclusive filter.
Karl
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160626/67a4a2a7/attachment.html>
More information about the swift-evolution
mailing list