Hello!
I am developing a survey of Swift utility libraries, with the goal of observing how the language is evolving in the wild. It was rightly pointed out that this could be a fantastic tool to generate new Evolution Proposals. (Writeup, scripts, etc on GitHub at GitHub - armcknight/colloquial-swift: Writeups and scripts for my survey of Swift utility libraries. README is a work in progress!)
Details on the survey:
Things I have automated so far:
- search github for Swift repositories matching certain criteria (e.g., name contains "util" or "extension"): 5,810 unique repositories currently returned
- focus on repos with podspecs (maybe carthage and spm one day): 1,357 repositories
- remove example/test/dependency directories: removed about half of over 500K files
- focus on declarations using
swiftc -print-ast
(avoids searching through comments/implementation) - classify by type of declaration:
func
,enum
, etc - especially focus on
extension
declarations and group all declarations they contain
From there I was able to do some quick command-line processing to aggregate and look for patterns. That's the next step that needs to be automated, as well as deciding which questions are actually important (and which ones aren't being asked yet). Here are some early things I found, which I presented recently at iOSDevCampCO.
Example findings:
Note: for all the lists, the first column of numbers are the frequency with which something was encountered.
- almost 13,000 extension declarations
- top 10 Swift stdlib APIs extended, with the number of extensions declared on each:
608 String
206 Array
204 Date
160 Int
119 Dictionary where Key : Hashable
90 Double
86 Data
68 Sequence
64 URL
62 Array where Element : Equatable
I drilled down into String to see how people are extending it. Here are the top 10 function declarations from extensions on String, in their canonical form from swiftc:
24 "trim() -> String"
13 "substring(from: Int) -> String"
12 "substring(to: Int) -> String"
11 "isValidEmail() -> Bool"
10 "trimmed() -> String"
10 "toBool() -> Bool?"
10 "height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat"
9 "trim()"
9 "toDouble() -> Double?"
9 "isNumber() -> Bool"
Trimming is not only the most popular function declaration, but appears many times in varied forms (84 total functions dealing with trimming):
24 "trim() -> String"
10 "trimmed() -> String"
9 "trim()"
3 "trimPhoneNumberString() -> String"
3 "trimNewLine() -> String"
3 "trimForNewLineCharacterSet() -> String"
2 "trimmedRight(characterSet set: NSCharacterSet = default) -> String"
2 "trimmedLeft(characterSet set: NSCharacterSet = default) -> String"
1 "trimmingWhitespacesAndNewlines() -> String"
1 "trimmedStart(characterSet set: CharacterSet = default) -> String"
1 "trimmedRight() -> String"
1 "trimmedLeft() -> String"
1 "trimmedEnd(characterSet set: CharacterSet = default) -> String"
1 "trimWhitespace() -> String"
1 "trimPrefix(prefix: String)"
1 "trimInside() -> String"
1 "trimDuplicates() -> String"
1 "trim(trim: String) -> String"
1 "trim(_ characters: String) -> String"
1 "trim(_ characterSet: CharacterSet) -> <>"
1 "stringByTrimmingTailCharactersInSet(_ set: CharacterSet) -> String"
1 "sk4TrimSpaceNL() -> String"
1 "sk4TrimSpace() -> String"
1 "sk4Trim(str: String) -> String"
1 "sk4Trim(charSet: NSCharacterSet) -> String"
1 "prefixTrimmed(prefix: String) -> String"
1 "omTrim()"
1 "m_trimmed() -> String"
1 "m_trim()"
1 "jjs_trimWhitespaceAndNewline() -> String"
1 "jjs_trimWhitespace() -> String"
1 "jjs_trimNewline() -> String"
1 "jjs_emptyOrStringAndTrim(str: String?) -> String"
1 "hyb_trimRight(trimNewline: Bool = default) -> String"
1 "hyb_trimLeft(trimNewline: Bool = default) -> String"
1 "hyb_trim(trimNewline: Bool = default) -> String"
I then looked at implementations just for the #1 form, trim() -> String
:
8 return self.trimmingCharacters(in: NSCharacterSet.whitespaces)
6 return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
5 return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
5 return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
3 return trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
3 return stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
3 return self.stringByTrimmingCharactersInSet(.whitespaceCharacterSet())
3 return self.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet())
2 return trimmingCharacters(in: CharacterSet.whitespaces)
2 return self.trimmingCharacters(in: CharacterSet.whitespaces)
2 return self.trimmingCharacters(in: .whitespacesAndNewlines)
2 return self.trimmingCharacters(in: .whitespaces)
1 return trimmingCharacters(in: .whitespacesAndNewlines)
1 return trimmed
1 return stringByReplacingOccurrencesOfString(" ", withString: "")
1 return strTrimmed
1 return self.trimmingCharacters(in: characterSet)
1 return self.trimmingCharacters(in: NSMutableCharacterSet.whitespaceAndNewline() as CharacterSet)
1 return self.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
1 return self.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines())
1 return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).trimmingCharacters(in: CharacterSet.whitespaces).trimmingCharacters(in: CharacterSet.whitespaces)
1 return (self as String).trimmingCharacters(in: CharacterSet.whitespaces)
1 return (self as NSString).trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) as String
This tells me that lots of people are solving the same problem the same way. But not all–there are enough differences here to warrant concern that behaviors could diverge now, or later if things like NSCharacterSet.whitespaceCharacterSet()
and (NSCharacterSet.whitespaceAndNewlineCharacterSet()
one day diverge for whatever reason (maybe not those specifically, but this is the general idea of seemingly-same-yet-different functions; and the more complicated the problem, the higher the impact of this divergence).
Conclusion
Lots of extensions out there solve the same problems, albeit with different implementations. We could think of those implementations on spectra of stability, usability, and others. It might then stand to reason that those most common tasks with the best implementations should be incorporated into the Swift stdlib (maybe with even better implementations).