When Swift 3 launched, it introduced a new concept of placing the preposition 
inside the parentheses. (See discussion here: 
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160208/009523.html).
 

I'm fine with that, however this change got implemented in an inconsistent 
manner, making Swift and its APIs more vague, thereby decreasing code clarity. 

I was hoping to see these inconsistencies get cleared up with Swift 4, however 
they were not. I realized that perhaps I could have spoken up and taken more of 
an active role in Swift Evolution. So... better late than never. Here are my 
thoughts, towards consistency and disambiguation in Swift.

Disclaimer: I’m sure the last thing anyone wants are more changes to the APIs. 
However, I also don't think these inconsistencies should be left in place 
forever. I have tried to go back and read through as much of the relevant 
discussions on list as I could, and it seems like 

Please let me some give examples below. After that is some discussion and a 
proposal.

EXAMPLES

Take for example the preposition "with." A preposition is meaningless without a 
subject and an object. Look at the following three sentences:

 (A) "A boy with the dog Molly walked across the street."

 (B) "With the dog Molly walked across the street."

 (C) "A boy with Molly walked across the street."

Sentence (A) is longest, but it makes perfect sense.

Sentence (B) is nonsensical and grammatically incorrect because while the word 
"with" takes two arguments, only one is present. If there was a comma after 
"dog," then it would make sense; "With the dog, Molly walked across the street" 
is fine. But if we were to assume that Molly is not the dog, in this case, we'd 
actually be wrong.

Sentence (C), while grammatically correct, leaves it unclear whether Molly is a 
dog, a girl, or… something else.  

The reason for this is, whenever a preposition is used in English, it almost 
always takes a dyadic form, relating a subject to the preposition's object. The 
two most common dyadic formats are:

<subject> [<preposition> <object of preposition>]
<The boy> [<with> <the dog>] crossed the street. 

[<preposition> < object of preposition>] <subject>
[<In> <space>], <no one> can hear you scream.
[<On> <the Moon>] are <many craters>.

Now, in Objective C through Swift 1 and 2, prepositions' dyadic nature were 
generally respected in method signatures. However, Swift 3's migration of the 
preposition inside the parentheses also seems to have been accompanied by the 
stripping away of either the subject, the prepositional object, or 
both—according to no discernible pattern. For example: 

(1) CloudKit:

old: myCKDatabase.fetchRecordWithID(recordID)
new: myCKDatabase.fetch(withRecordID: recordID)
(subject "Record" got removed)

(2) String:

old: myString.capitalizedStringWithLocale(_: myLocale)
new: myString.capitalized(with: myLocale)
(subject "String" and prep. object "Locale" both got removed)

(3) Dictionary:

old: myDict.removeAtIndex(myIndex)
new: myDict.remove(at: myIndex)
(subject "element" already missing from both; prep. object "Index" got removed)

(4) Date:

old: myDate.timeIntervalSinceDate(myDate)
new: myDate.timeIntervalSince(date: myDate)
(subject "timeInterval" and object "Date" both still present; but oddly, 
preposition "since" got left outside of the parentheses)

(5) Array:

            old: myArray.forEach({ thing in code})
new: myArray.forEach() { thing in //code }
            (preposition “for” is outside of the parentheses)

DISCUSSION OF EXAMPLES

These four changes are inconsistent with each other regarding whether the 
subject, prepositional object, or both were removed, and we don't even have 
consistency as to whether the preposition got moved inside the parentheses (see 
A below). 

As well, these changes generally removed the dyadic arguments to the 
preposition, which removes cues necessary to disambiguate the prepositional 
relationship, decreasing code readability (see B below).

(A) Inconsistency

The inconsistency between the examples is shown in the bold text of each 
example, but lets go over why this matters. It matters because any language is 
easier to learn the more consistently it sticks to its own rules. Autocomplete 
is our great savior, but still, if we were being consistent, then the new 
method signatures would have been:

(1) myCKDatabase.fetchRecord(withRecordID:)
(2) myString.stringCapitalized(withLocale:)
(3) myDictionary.elementRemoved(atIndex:)
(4) myDate.timeInterval(sinceDate:)
(5) myArray.each(inClosure: )   

Side note: for plain English readability, we might prefer 
elementRemoved(fromIndex:) and stringCapitlized(accordingToLocale:). 

Although I do understand removing "string" from the latter was to reduce 
redundancy in function/method declarations, we only make one declaration, yet 
we make many calls. So increasing ambiguity in calls does not seem like a good 
trade-off for decreased boilerplate in declarations. More often than not it's 
calls that we're reading, not the declarations—unless of course the call was 
ambiguous and we had to read the declaration to make sense out of it. So 
perhaps we might question if increased ambiguity is an overall good thing. 

Side note: example (5), .forEach, does seem like a very exceptional case, and 
possibly a sacred cow. See the Proposal section for further discussion of this.

(B) Increased Ambiguity

In all of these changes, one of or both parts of the dyadic arguments of the 
preposition have been excised from the method signatures. This increases 
ambiguity in Swift 3/4 vs. Objective C and Swift 2, especially in closures, as 
I will explain below.

In example (1), the old method argument makes grammatical sense because "record 
with RecordID" follows the format, <subject> [<preposition> <object of 
preposition>], just like "boy with dog tag" or "cow with black spots." This 
improves code readability, because when you read this, you know that this 
function will give you a record matching a particular recordID. However in 
Swift 3, we have the equivalent of, "with a recordID"—i.e. is *implied* that 
the thing being fetched is a record. 

This isn't a problem you're reading the method signature in the header, and 
it's not a problem when you're writing the code, because you'll get warnings 
and errors if you try to assign to the wrong type.

However this removal of explicit contextual cues from the method signature 
harms readability, since now, the compiler will let people write code like:

{ return $0.fetch(withRecordID:$1) }

Clearly, the onus is now on the developer not to use cryptic, short variable 
names or NO variable names. However, spend much time on GitHub or in CocoaPods 
and you will see an increasing number of codebases where that's exactly what 
they do, especially in closures.

Another problem is that the compiler doesn't care if you write:

{ ambiguousName in
let myRecordID = ambiguousName.fetch(withRecordID:myID) 
return myRecordID }

This is highly problematic because someone reading this code will have no 
reason to expect the type of "myRecordID" not to be CKRecordID. (In fact, it's 
CKRecord.) There is also no way to clarify what ambiguousName is since closures 
don't have argument labels at all... but that's another problem altogether.

Turning now to example (2), "myString.capitalized(with:myLocale)" sacrifices 
BOTH the subject and the prepositional object. We have now lost any enforced 
contextual cues, fully orphaning "with", allowing and even encouraging a final 
closure argument like: 

{ return $0.capitalized(with: .current)) }

What is $0? Reading this, and being familiar with Swift, you will likely assume 
it's a string, but will you remember that .current is a Locale? The compiler 
knows, but why was it a good idea to strip out that information? 

We also have examples like: 

{ return $0.draw(with:$1) }

What is $0? What is $1? This is a real Apple API, BTW.

We could also have:

{array, key in 
let number = array.remove(at:key)
return number }

This will compile and run even though number will be a tuple key-value pair, 
array will be a dict, and key will be an index type! This may seem like a 
ridiculous example, but I have literally seen things like this.

DISCUSSION

Making computer code more like natural language and thus more approachable to 
non-computer-science types was clearly one of the primary goals of Apple's 
method names in Objective C. 

I've been a fan of the general direction in which Swift has taken things, but 
I'm starting to see code that is quite unreadable as a result of the stripping 
away of useful information from code, as if more white space will improve our 
ability to make sense out of our arcane invocations.

The point of code readability is for humans to be able to read and understand 
code with minimal extra effort. Explicit information is important because 
humans would like to read code without having to dig through five different 
headers and read lots of documentation files. This directly increases costs, 
because when a new developer comes onto a project, the longer it takes them to 
understand the codebase, the more it will cost the company.

I don't believe the intent with these changes was to increase ambiguity; at 
WWDC '16 we were told it was to increase clarity. I'm just not sure that's the 
consistent effect we actually received. 

What we got instead is more times we will need to CMD-click things in XCode to 
get a clue. That's especially annoying because you're at XCode's mercy as to 
whether it actually wants to jump to a given declaration or not. This seems 
pretty hit or miss in my experience, even within a basic playground page, and 
is especially troublesome with jumping to declarations within CocoaPods. 
Selecting some code to get documentation in the side panel seems equally 
unreliable. 

Perhaps CMD-click problems have to do with a project setting, but is there 
really a time when you would ever NOT want to have these capabilities? Why is 
it tied into a setting? Further, when you're reading through a repo on GitHub 
itself, you don't have CMD-click capability; you want the code to be as clear 
as possible, without lots of ambiguity.

From Ambiguous to Cryptic

Orphaning method signatures by stripping useful return type and argument type 
information wouldn't be so bad if variables were all named descriptively, but 
that is a strangely optimistic hope for a language that's as paranoid about 
safety that it was specifically designed to prevent many categories of common 
mistakes.

The increased ambiguity in Swift from the removal of prepositional components 
has been further compounded by other forms of ambiguity that are increasingly 
everywhere in Swift, and which developers have taken copious advantage of to 
the point of abuse. Examples are:

- arguments labelled as "_" reduce code clarity
- calls to functions that take closures as the final argument can leave the 
final argument label out, decreasing readability
- closures have lack explicit type information, leaving it up to variable names 
to let us know what's going on; however, the use $# syntax and/or cryptically 
named variables often leaves it totally ambiguous
- generic types in method signatures using a single letter give no clue as to 
the type's purpose
- function types can't have argument labels, which might give a sense of 
context or informative cues, even if they added boilerplate 
- inferred nested type parents (like the ability to do .current instead of 
being forced to do Location.current)

Why is removing context and clue good?

Is it possible that this was an overreaction to the perceived over-verbosity of 
Objective C? I'm honestly curious. I will try to read back through the 
archives, but perhaps someone can give me a good summary.

In the old Objective C style, method names could be quite long, but I never 
felt a lack of context that made code unclear. The method signatures were 
basically like sentences without spaces, and they seemed to follow pretty 
reliable rules. So reading them actually made grammatical sense, improving code 
readability. I could always tell what a call was doing.

While I won't deny that Objective C went overboard sometimes in verbosity, and 
while few would dispute that we should clean up its style, could it be we are 
going too far making Swift encourage code that's ambiguous to everyone except 
the compiler? Could it be a mistake for Swift to err so far on the side of 
inferring everything and stripping away contextual information?

A first-time reader of some unseen code is not going to be able to infer things 
like the original programmer or the compiler can. This increases the 
possibility of bugs (and exploits) sneaking in "under the radar."

PROPOSAL

To add more clarity back to Swift, I'd propose the following rules be applied 
consistently across the language:

• Every time a preposition shows up in a function/method signature, there is an 
explicit subject and object of the preposition, except in the following 
exceptions.

• The subject of a preposition can be left out if the function/method is 
mutating AND doesn't return a value, such as Array's .append(contentsOf:) 
method. This method would become: .append(contentsOfCollection:) so that it's 
clear what we're doing here, even within a closure.

• No preposition should be used when it would add no extra meaning between 
what’s inside and outside the parentheses. For example, userFont(ofSize 
fontSize:) should just be userFont(size: fontSize), because “of” is completely 
superfluous here. AVFragmentedMovie’s track(withTrackID:) could just as easily 
be track(forTrackID:) or track(TrackID:) which tells us that the preposition is 
superfluous and so why not just have it be track(id:). 

• If the preposition goes with a verb in the method signature, the verb should 
be the last thing before the opening parenthesis (e.g. String's .capitalized 
function would now look like name.stringCapitalized(accordingToLocale: 
.current)... no pun intended).

• In the case of .forEach, since it’s almost identical in function to .map, 
perhaps a solution could be to eliminate .forEach entirely, and simply say that 
anytime .map’s result is unused (or assigned to _), then the compiler will 
automatically use .forEach functionality to optimize performance. For example 
if in Swift 5 you did _ = myArray.map() { print($0) }, this would be the same 
as doing myArray.forEach() { print($0) } currently. As well, _ = could be 
inferred. 

Note: part of the problem is obviously that parameter names are never used in 
function calls when there is an argument label, but often, the argument label 
is now just a preposition, while its prepositional object is the (now hidden) 
parameter name. Perhaps we can think of a novel solution to this?

I'm interested to know what people think about this. Perhaps there are other 
ideas or ways to add clarity back in. Thanks for reading this long message. 

Jon
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to