(De)Pluralization for Types

Swift 4’s Codable can auto-generate a type called CodingKeys. That “s” has got to go!

CodingKeys represents what I’ve observed to be a prevalent lack of standardization involving pluralization and de-pluralization in type names. I think I understand why Apple hasn’t gone with what I would have, but I find it intriguing that anyone involved thought that pluralization was an acceptable solution.

I figured I’d offer a tiny reference on pluralization because I haven’t seen one yet. I’m sending this along to Apple with my relevant bug report, that they’ve closed, in case it will be helpful. I’d love to see this kind of thing improved, expanded, and expressed in official documentation, instead.

A type’s name is not the name for the collection of its instances.

…Instead, a type’s name is the name for the type of any of the type’s instances.

Once you make sense of that, it might seem obvious to you, from experience with most types: they represent the concept of a singular entity, such as a number, or a smilodon. Their definitions look like so:

struct Int // …lots o' code

final class Kitty {
  let name: String
  
  init(name: String) {
    self.name = name
  }
}

Non-enum types are (typically unfathomably enormous) enumerations without case names.

Let’s create an Int:

let five = 5

There are other 5s whose type is not Int, but an Int is what we’ve got there.

Have another Int!

let negative10 = -10

Both of those constants are instances of Int. The type’s name is not Ints, despite the type being the equivalent of an enumeration for all integers.

(Conceptually, Int should yes, really be all of them. The fact that we’re limited to a -(2^x) to 2^x - 1 range is a hack to get things done, practically, with our cave-people computers.)

Following with that, these pals of Catie’s and mine are not of type Kitties. Just Kitty:

let gobo = Kitty("Gobo")
let ozma = Kitty(name: "Ozma")

Whether or not “enumeration” is the right term for this, the set of discrete instances that a type can represent is usually based on what you define for its Equatable conformance.

extension Kitty: Equatable {
  static func == (kitty0: Kitty, kitty1: Kitty) -> Bool {
    return kitty0.name == kitty1.name
  }
}

There are a whole lot more Strings you can make, than Ints, so you can consider Kitty a larger set of cases than Int. But as far as Kitty is concerned, all kitties with the same name are the same “case”.

let somebodyElsesDifferentPalNamedOzma = Kitty(name: "Ozma")
somebodyElsesDifferentPalNamedOzma == ozma // true 🙀

Enumeration cases, and get-only type properties that return type instances, represent the same idea.

Apple states to, “Give enumeration types singular rather than plural names, so that they read as self-evident”.

I doubt that anything is actually “self-evident”, but certainly in this case, it sounds elitist and subjective to me. But at least they’re right. 😺 enums are no different than other types, in that their name is actually for any one of their instances.

The following two types utilize the same concept: singling out two particular Strings as being “green face strings”.

For switch statements, or other places where the concept of a “green face” would be best modeled by a type, actual cases are the way to go:

enum GreenFace: String {
  case frog = "🐸"
  case human = "🤢"
}

Otherwise, what you’re dealing with is akin to a sub-enumeration of the larger “enumeration” known as String:

enum GreenFaceString {
  static let frog = "🐸"
  static let human = "🤢"
}

Moving the name of that superset type to the end of the type name visually reflects the subclassing-esque relationship. Though the compiler can only tell you that each one of these is a String, not a GreenFaceString, we know the more precise truth. Using a pluralizing “s”, for GreenFaceStrings, would be inaccurate. Again, both frog and human are only a String, each. Not a Strings.

Note, Swift doesn’t actually provide a mechanism to create the specific type we’re going for with GreenFaceString. You can’t enforce that all type properties return an instance of the parent type. That even goes for enums, as they can house static properties.

Personally, I believe I’m fine with the mental convention of appending the name of a type to all its static properties whose names are otherwise meaningless, and don’t include the name of another type. That “otherwise meaningless” is too fuzzy for me to be happy with, but represents the best I have right now. Any properties that require such a mental appending should neither add nor remove plurality from the type name. Here are some examples from Bundle:

Bundle.main: This returns a instance at the same level of plurality as the type name. It’s the “main Bundle”. There’s not a type called “Main”, so it’ll do nicely.

Bundle.allBundles: This returns an “Array of Bundle Elements”. Pluralization is increased, so even though Bundle.all might be clear enough, it’s not used. Good work!

Apple’s rationale for CodingKeys

As expressed in my bug report reply, here’s one reason why we’ve got an “s”:

…the `CodingKeys` type acts more as a _namespace_ for keys, rather than an enumeration itself. It would be exceedingly rare to need to type “var key = CodingKeys.myProperty”. Instead, the naming was chosen to be read in context: “encoder.container(keyedBy: CodingKeys.self)” should read as “a container keyed by my coding keys” (“keys” here, being a plural word, is more intuitive than “key”). Note that because the keyed container is generic on the key namespace, it’s never necessary to refer to the full type again: “container.encode(myProperty, forKey: .myProperty)”, not “container.encode(myProperty, forKey: CodingKeys.myProperty)”. The type is only referred to explicitly once, and the individual cases afterwards are “self-evident”.

One more “self-evident”, and a “more intuitive” to boot! 😜 Both are at least personal, but to me, they’re imaginary. Everyone’s senses of self-evidence and intuition are malleable. But I’m not convinced anyone actually believed in this to begin with. I think they added an “s” because pluralization standards don’t exist yet, and some people are used to naming their enumerations or collections of constants with plural names. CodingKeys doesn’t feel wrong to them yet, and the pluralization avoids what would have been a problem in Objective-C. (I get to it in the last section.)

Some Types Warrant Plural Names

Option Sets

An instance of a type that implements the OptionSet protocol is an “options”. You might have an “options” whose bit pattern represents only one option being included, but it also simultaneously represents the lack of inclusion for the other options.

You give a more specific name to the “options” via the type name. Here, the options are colors:

struct Colors: OptionSet {
  static let orange = Colors(rawValue: 1 << 0)
  static let purple = Colors(rawValue: 1 << 1)
  static let green = Colors(rawValue: 1 << 2)
  
  let rawValue: UInt
}

Regardless of how many color options are included in an instance, its type is always Colors.

var colors: Colors = [.orange, .green]
colors = []
colors = [.purple]

Plural is good for collection instances, but not types.

Collection (and other subscriptable) instances are typically clearly expressible as a plural version of the type of their Element, rather than being named as a singular form of the collection type. For example, colors is used here instead of colorArray, despite its type in fact being Array<UIColor>.

let colors: [UIColor] = [.orange, .purple, .green]

Why don’t Array, Set, or other sequences or collections themselves have plural names? Here are some reasons I can think of:

  1. Types are too different from each other for a generic plural name to describe them all well. “Objects” is true of some languages, but for whatever reasons, value type instances don’t seem to be considered “objects”. What would the generic name be? Thingies?
  2. You’ve got to disambiguate between the types. They do actually have different capabilities, and can’t all have the same nondescript plural name.
  3. The type name represents a superset of the actual type of thing we need, not something specifically-tailored for any use case. Array is the worst offender; those angle brackets make it all too easy to wrap a few things up! So we use it all over the place. From the declaration, you can’t tell if type of colors really ought to be ColorArray, or if ColorSequence, or in fact, just Colors might do…

Subscripts typically decrement plurality.

Picking out exactly what’s needed, and implementing an appropriate collection type, is complex and time-consuming. I do think that compilers will get us to the point that our types will be generated based on more specific needs, but have no idea when that will happen or if anyone is working on such a project.

However, I have found that there are plenty of times where I know I don’t need most capabilities of any particular Collection, even an immutable instance of one. Nor do I need to iterate a Sequence. Instead, I just need something I can provide an “index” to, and then get an instance of a specific type back. If you’ve made it this far into this post, I bet you’re familiar with what that typically looks like: you subscript into something with a plural name, and you get a singular name back. Like so:

let color = colors[2]

If that’s all you plan to do, with an instance, the capability of anything from the standard library is overkill, and not a good representation of what your type is. If we take it as a given that subscripting accesses a singular version of something plural, type names for these use cases can be UpperCamelCase versions of what the instances are called. In this case, colors‘s type would be Colors.

Unfortunately, it’s not as simple as it should be, to model such types in Swift. I think they might be called “named subscripts” in this language; elsewhere, I’ve seen “named indexers”.

Comprehending this next block of code isn’t necessary to understand what named subscripts are, but here’s the get-only Swift 4 version that Catie and I have found to be suitable for most cases. As with arrays and dictionaries, most subscripts don’t require argument labels, to make sense. This code can’t support argument labels because closures in Swift throw them out. 🚮😿

public struct NamedGetOnlySubscript<Index, Value> {
  public typealias GetValue = (Index) -> Value

//MARK: private
  private let getValue: GetValue
}

public extension NamedGetOnlySubscript {
  init(_ getValue: @escaping GetValue) {
    self.getValue = getValue
  }

  subscript(index: Index) -> Value {
    return getValue(index)
  }

  subscript<Indices: Sequence>(indices: Indices) -> [Value]
  where Indices.Element == Index {
    return indices.map {self[$0]}
  }

  subscript(indices: Index...) -> [Value] {
    return self[indices]
  }
}

To make it so that you can do something like this…

DispatchQueue.globalQueues[.background]
DispatchQueue.globalQueues[.userInteractive]

…you need a definition like this:

extension DispatchQueue {
  static var globalQueues: NamedGetOnlySubscript<DispatchQoS.QoSClass, DispatchQueue> {
    return NamedGetOnlySubscript(global)
  }
}

Plural names are great for named subscripts.

Plural typealiases work well for specialized versions of something generic like NamedGetOnlySubscript. But plural names work great for original type names, when those types only exist to forward along subscripts. GlobalQueues is such a type name, here:

public extension DispatchQueue {
  struct GlobalQueues {
    public subscript(qos qos: DispatchQoS.QoSClass) -> DispatchQueue {
      return getGlobalQueue(qos)
    }
    
    fileprivate let getGlobalQueue: (DispatchQoS.QoSClass) -> DispatchQueue
  }

  static var globalQueues: GlobalQueues {
    return GlobalQueues(getGlobalQueue: global)
  }
}

With a customized named subscript, we have the ability to preserve the original parameter name:

DispatchQueue.globalQueues[qos: .utility]

Wait …why bother with a subscript instead of a method?

Syntactically, subscripts offer only one piece of functionality that methods don’t: the ability to use angle brackets in lieu of a method name and parentheses. Named subscripts don’t even have that going for them – ’cause they need a name!

And in fact, subscripts are more poorly-featured, in general. Although Swift 4 brought us generic subscripts, they still can’t throw, or have default arguments. But because of how frequently we all need containers into which we can get and/or set something, and that’s what subscripts do, they either ought to be standardized for that use case or completely removed from the language.

Before closures were available, having methods with singular noun names, like DispatchQueue.global was reasonable. But with the ability to store functions in variables, such naming ceases to make sense.

Does this name look right to you?

let globalDispatchQueue = DispatchQueue.global(qos:)

To me, that looks like it’s defining a constant that holds a global DispatchQueue. It’s not. And the truth is clear enough when you use the closure…

let queue = globalDispatchQueue(.userInitiated)

…but until then, it feels like a lie to me. The closure is not a global DispatchQueue. It’s the get accessor for a global DispatchQueue. And in Swift, a get accessor that requires parameters is expressed using a subscript.

For Catie’s and my part, if subscripting doesn’t work, we tack on the word get. This is part of the larger rule that we always begin our function and closure names with verbs. That hasn’t failed us yet, though it’s a poor substitute for the ability to assign closures directly to accessors – what the naming emulates.

extension DispatchQueue {
  static func getGlobal(qos: DispatchQoS.QoSClass = .default) -> DispatchQueue {
    return global(qos: qos)
  }
}

let defaultGlobalDispatchQueue = DispatchQueue.getGlobal()
let getGlobalDispatchQueue = DispatchQueue.global(qos:)

“So, yes, Jessy. I see where your blah blah blah is coming from. What ought to be the name?!”

CodingKey.

Yep. Your type’s CodingKey implements Swift.CodingKey. It doesn’t look any different than subclassing a class with the same name, from another module, for example.

struct 🗝: Codable {
  private typealias CodingKey = CodingKeys
}

Potential confusion is the reason Apple gave me for why that’s not what they used.

Although it would be possible to disambiguate between the `CodingKey` protocol and a type implementing that protocol explicitly (as you say, `Type.CodingKey` vs. `Swift.CodingKey`), requiring this approach would very easily lead to a lot of confusion. Accidentally using a protocol type instead of a concrete type (or vice versa) in many places leads to confusing compiler diagnostics; there are many developers who are not aware of this disambiguation, and this approach would set them up to fail by default, running very counter to the “pit of success” that we strive for. We don’t do this elsewhere in the standard library either (consider the `IteratorProtocol` protocol, which is so named so that it doesn’t conflict with the `Iterator` associated type on the `Sequence` protocol).

I don’t think they’re right, but their target audience may still be people who worked in non-namespaced languages (Swift is still that), without nested types (Objective-C is still that), for most of their lives. Although I’m not happy to have simplicity sacrificed for non-universal confusion mitigation, as long as CodingKeys gets a new name that is singular, and makes sense, it will be an improvement.

Leave a Reply

Your email address will not be published. Required fields are marked *