[swift-evolution] Stdlib closure argument labels and parameter names
Dave Abrahams
dabrahams at apple.com
Mon Jun 27 08:05:00 CDT 2016
on Sun Jun 26 2016, Erica Sadun <erica-AT-ericasadun.com> wrote:
>> On Jun 26, 2016, at 10:35 AM, Dave Abrahams <dabrahams at apple.com> wrote:
>>
>>
>> on Sat Jun 25 2016, Erica Sadun <erica-AT-ericasadun.com
>> <http://erica-at-ericasadun.com/>> wrote:
>
>>
>>> On Jun 25, 2016, at 4:25 PM, Dave Abrahams <dabrahams at apple.com> wrote:
>>>> on Wed Jun 22 2016, Erica Sadun <erica-AT-ericasadun.com> wrote:
>>>> On Jun 20, 2016, at 3:25 PM, Dave Abrahams via swift-evolution
>>>> <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) {
>>
>> OK, I understand your process now. I still don't understand your
>> rationale for saying `do` or `perform` is better than `invoke`. Or is
>> it just personal taste?
>
> See below. But in a nutshell, `do` gets the idea across. It's short. It's pithy.
> It uses a common, comfortable word. So yes, personal taste. But it's personal
> taste backed up by some statistics.
I get the reasons for `do`; I can't understand how `perform` might be an
improvement over `invoke`.
>>>>> 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`.
>>
>> Ah, thanks.
>>
>>>>> -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.
>>
>> I'm unable to reproduce those numbers using
>> http://corpus.byu.edu/coca/ <http://corpus.byu.edu/coca/>.
>> What exactly did you do to get them?
>
> I used a secondary webpage that rates word frequency based on the COCA corpus:
> http://www.wordfrequency.info/free.asp?s=y
>
>> Also, I'm not sure commonality of use is a good rationale. I bet
>> “function” doesn't appear as often as “task” either, but the argument
>> here is a function and we invoke functions. Sometimes the appropriate
>> word is just less common.
>
> 1051 task
> 1449 function
>
> Numbers aside, there's always a "term of art" argument to be made. A term of
> art is precise and instantly communicates the meaning to the user.
>
> I don't think a "term of art" argument can be made here since invoke,
> perform, and do all communicate the same idea to the end-coder, who
> doesn't need to know exactly how the compiler sees the argument.
>
> In naming theory, I'd say the member name carries the greatest weight
> when using exact terms and that internal labels should accessorize the
> primary name. When using a "term of art" argument, it should apply
> most strongly to a method/function/ property/type name and less to
> supporting external labels.
>
>>>>> - 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.
>>
>> I plead guilty to sometimes having unnecessarily fussy tendencies, but
>> in this case I believe strongly that “byComparing” would be actively
>> misleading and harmful. Even if there's only one possible sane
>> interpretation, if readers have to scratch their heads and do an
>> exhaustive search through possible interpretations of what something
>> might mean because the one directly implied by the grammar is nonsense,
>> that's bad.
>
> "Comparing by" is not actively misleading or harmful but it does sound
> strained.
Absolutely.
> I think a well-designed language should use comfortable constructs.Why
> not use a single , plain word that describes a parameter's role. Why
> not "compare:"? It's far less fussy and it's clear.
Yes, we can do that at the cost of fluency. Fluency isn't everything.
>>> 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:`.
>>
>> There is definitely something awkward about “xxxBy:” for these uses, and
>> I'm open to using “xxxWith:” instead, even though as you say it's
>> confusable.
>>
>> if x.starts(with: y, comparingBy: { $0.name == $1.name }) {
>> ...
>> }
>>
>> if x.starts(with: y, comparingWith: { $0.name == $1.name }) {
>> ...
>> }
>>
>> At some point I start to wonder if giving up fluency is best:
>>
>> if x.starts(with: y, equality: { $0.name == $1.name }) {
>> ...
>> }
>
> Yes. `predicate`, `compare` (or `comparison`), `order:`, etc.
>
> To riff off Gerard Manley Hopkins, "Glory be for simple things, for
> short words and trim APIs".
>
>>>>> 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.)
>>
>> I agree, but if you want concision I'd rather stick with something that
>> doesn't imply anything unintended, such as “to” or “into.”
>
> I much prefer `to` and `into`
>
>>>>> - 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.
>>
>> That doesn't mean we should claim the syntax for another purpose.
>> I think we probably do want that feature eventually.
>>
>>> 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.
>>
>> IMO whether a method has a non-Void return value (and thus is chainable)
>> should not be the determining feature on whether we use argument labels.
>
> If any method returns a sequence or collection, I think it *should* be
> considered in that specific light as a candidate for chaining. I agree
> that expanding that to any return value is too wide.
>
>> We *could* decide to drop all labels for trailing closures (on the
>> grounds that they often won't appear at the call site) and say the
>> policy is that if there's something you want to communicate about the
>> closure's role, you simply do the best you can with the parameter name
>> and let the clarity at the call site fend for itself. That, at least,
>> would be a reasonably principled approach.
>
> I like labels for trailing closures when they are `-> Void`. You know a priori
> that the closure is used for side-effects, so if they are put into a function or
> method call, the label alerts you to design intent.
>
> I want to omit labels for trailing closures when they return collections, sequences,
> iterators, etc. because these return types are most likely to be chained.
>
> If you force a label for, say, map, people will choose
>
> map { ... }
>
> over
>
> map(label: { ... })
>
> because the former reads better, especially when chaining but it introduces
> bad habits, because it will not compile when used with constructs like:
>
> for i in map { ... } { // will not compile
> ...
> }
I don't think Swift wants to buy into the idea that using map without
trailing closures is a bad habit. We strongly believe in trailing
closure syntax. I would much rather find ways to make the syntax above
legal. Furthermore, the logic doesn't follow for me. There are other
ways to get that to compile, such as
for i in (map { ... }) { // will not compile
Why not make the argument that it's a bad habit to call map without
*surrounding* parentheses?
> But if you treat collection/sequence fp as "highly likely to be
> chained", it encourages
> results more like:
>
> for i in x.map({ ...}).filter({ ... }) {
> ....
> }
>
> which is compiler-safe, more readable, etc, especially for very
> short-form items like:
>
> for var i in x.flatMap({Int($0)}).filter({ $0 < 10 }) {
I disagree about readability. One set of surrounding parentheses is
clearer
for var i in (x.flatMap{ Int($0) }.filter{ $0 < 10 }) {
Further, I am concerned about pervasive inefficiency when people try to
pack this kind of chaining into a for loop with as few characters as
possible. Generally speaking, it would be better to do it lazily.
>>> 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{...} {
>>> ...
>>> }
>>
>> ? It's never confused there; it tells you it's illegal.
>>
>>> and instantly identifies to the reader when there's a non-returning scope, as
>>> in forEach or GCD dispatch.
>>
>> Why is it useful to identify when there's a scope that doesn't return a
>> value? Isn't that already clear from whether the value is assigned
>> somewhere?
>
> Enhanced readability from explicit coding intent. Trailing closures
> *look* like a scope created by a native construct. Those enclosed in
> parens *look* like arguments.
I understand what you're saying, but I think it's unnecessarily fussy
;-)
two-can-play-at-that-game-ly y'rs,
--
-Dave
More information about the swift-evolution
mailing list