Cheap Orders

How to create order? If the second law of thermal dynamics tells us anything, we'd better get to work, right? {: .lead}

Before going full existential, let's limit "order" to Swift's set and dictionaries -- there is none. Of course, you can take values/keys out and sort them. But what if all you care about is some order?


Recently, I wrote

enum Token {
  static let all = [
    "=": .Equal,
    "-": .Minus,
    // more token mappings …
  ]
  case Equal
  case Minus
  // more tokens …
}

… in the hope that I can take each value from Token.all.keys and see if a prefix of a string is a matching token. It started to fail as the tokens expands to multiple characters:

enum Token {
  static let all = [
    "=": .Equal,
    "-": .Minus,
    // more token mappings …
    "==": .Equality,
    "->": .Arrow,
    // more token mappings …
    "===": .Identity,
    // more token mappings …
  ]
  case Equal
  case Minus
  case Equality
  case Arrow
  case Identity
  // … more tokens …
}

-> could get a match with - and === would match to either == or =, etc.

Since the tokens in this exercise have at most 3 characters, I decided to group them by length and match from the longer group first. The groups became:

enum Token {
  static private let group1 = [
    "=": .Equal,
    "-": .Minus,
    // more token mappings …
  ]

  static private let group2 = [
    "==": .Equality,
    "->": .Arrow,
    // more token mappings …
  ]

  static private let group3 = [
    "===": .Identity,
    // more token mappings …
  ]
}

Now I can choose which group to take values first. There's a way to do it without adding some control flow logic:

[group3.keys, group2.keys, group3.keys].flatten()

Even better, I'll make it a lazy property…

enum Token {
  static var all = {
    [group3.keys, group2.keys, group3.keys].flatten()
  }()
}

…except an important piece of information is missing from the property: what's all's type? Turns out, it's become

FlattenCollection<Array<LazyMapCollection<Dictionary<String, Token>, String>>>

Ahh, it seems that in the pursue of cheap, lazy creation of these structures, we are forced to deal with a bunch of type wrappers, each having a good reason to be here!

But I really just need something like Array<String> for the consumer. If only there's a way to make all this stuff go away from my type signature, as if they are erased :).

Okay, I'm talking about AnySequence now. Rob Napier has an excellent post on this topic here if you need to catch up. Our code eventually end up like this:

enum Token {
  static var all: AnySequence<String> = {
    AnySequence(
      [group3.keys, group2.keys, group3.keys]
        .flatten())
  }()
}

Instead of Array<String>, we have an AnySequence<String>. Our tokens now gets checked with the correct order. We didn't need to sort the entire set of tokens, nor did we do any heavy data massage upfront, making a bunch of copies along the way.


Looking back, this post really failed to capture the eureka moment as I came up with the erasure method. I discovered a series of small challenges and got help from Swift's designers in each step. Everything fell together in the end.