Improving Code with Generics

Update: I updated the post to make use of S: SequenceType instead of T: GeneratorType; it's a cleaner API.

: .info

Yesterday, I wrote about how we needed to build the following class:

struct UnicodeScalarParsingBuffer {
    var generator: String.UnicodeScalarView.Generator
    var current: UnicodeScalar? = nil

    init(_ generator: String.UnicodeScalarView.Generator) {
        self.generator = generator
    }

    mutating func next() -> UnicodeScalar? {
        self.currentUnicodeScalar = generator.next()
        return self.currentUnicodeScalar
    }
}

When we look at the code above, we can observe a few things:

  1. The code is tightly coupled to String.UnicodeScalarView.Generator
  2. The code is tightly coupled to UnicodeScalar
  3. The code loosely conforms to GeneratorType

We can make this code better and more suitable for other instances of GeneratorType; or to put it another way, generic.

Let's start from bullet #3; we should be conforming to the GeneratorType protocol because this really is simply another type of generator.

The definition starts to take shape like this:

struct BufferedGenerator : GeneratorType {
    var generator: GeneratorType
    mutating func next() -> UnicodeScalar?
}

Bullets #1 and #2 are aspects of the same coin as Generator and Generator.Element are really defined from the same construct.

The interface now looks more like this:

struct BufferedGenerator<S: SequenceType> : GeneratorType {
    typealias Sequence = S

    var generator: Sequence.Generator
    var current: Sequence.Generator.Element? = nil

    init(_ sequence: Sequence) {
        self.generator = sequence.generate()
    }

    mutating func next() -> Sequence.Generator.Element? {
        self.current = generator.next()
        return self.current
    }
}

This implementation now let's us use any type of SequenceType as a BufferedGenerator.

We use SequenceType as the generic constraint instead of GeneratorType because it creates a better ownership model for the underlying generator. The call to next() should only be done from a single generator; this code puts that burden on BufferedGenerator<S> instead of the caller.

: .info

Generics can be a great way to reduce type information that simply doesn't need to be there. In this case, there was no reason that the original UnicodeScalarParsingBuffer needed to be tied to a specific type. Generics can also help greatly in code reuse, which is almost always a good thing.

The full source for the json-swift library can be found over on GitHub.

Improving Code with Generics