[CONFIRMED] Typo in the Associated Types section

In the Associated Types section of the Generics chapter, just after Listing 21.16 the text provides a definition of the Sequence protocol:

protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    associatedtype Element where Element == Iterator.Element
    func makeIterator() -> Iterator
}

In the third paragraph that follows, it says:

Sequence also requires conforming types to implement a single method, makeIterator(), which returns a value of the associated type IteratorProtocol.

Am I wrong or is the word “IteratorProtocol” (shown in bold above) a typo? Seems like they meant to use the word “Iterator” instead.

The Sequence protocol definition shows that makeIterator() returns a value of the Iterator type. Not the IteratorProtocol type. The definition shows that the associated type Iterator must conform to IteratorProtocol. But the associated type itself is still just Iterator, right?


EDIT:
Typo was confirmed by @MikeyWard. The following sentence:
“…which returns a value of the associated type IteratorProtocol.”

should actually read:

“…which returns a value of the associated type Iterator.”

Unlikely to be a typo.

Yes, but it also conforms to IteratorProtocol.

Hence:

func makeIterator() -> Iterator

rather than:

func makeIterator() -> IteratorProtocol

@ibex10

I guess I still don’t understand generics well. Are you saying that just because a type conforms to a protocol, then its type changes to become the protocol’s type? Having a type of Iterator is not the same as having a type of IteratorProtocol, is it?

In my playground file for this lesson, when I Option-click the makeIterator() method, the pop-up window shows that makeIterator() should return a type of StackIterator<Element>. Not a type of Iterator.

This is because Listing 21.17 tells us to write the method that way. So perhaps the paragraph preceding Listing 21.17 which says, "…returns a value of the associated type IteratorProtocol" is referring to that method? And not the preceding Sequence protocol definition?

It’s kind of tricky. IteratorProtocol is not a type, but Iterator is not a specific type either. You’ll notice back in listing 21.14 that StackIterator<T> was defined as a type conforming to IteratorProtocol, and that is what is being returned by makeIterator in listing 21.17. There is no type called Iterator, it only shows up in the declaration of Sequence as a placeholder name for whatever iterator type you wind up defining.

It would probably be better if the sentence in the paragraph preceding listing 21.17 read “returns a value of a type conforming to IteratorProtocol”, since that’s the real requirement.

Edit: OTOH, go back and read the paragraph preceding listing 19.8:

Protocols do not just define the properties and methods a conforming type must supply. They can also be used as types themselves. You can have variables, function arguments, and return values that have the type of a protocol.

Reading the offending sentence in that light, it makes sense.

2 Likes

@JonAult

Okay, that makes sense. The way you phrased it, “returns a value of a type conforming to IteratorProtocol” is very helpful. I can understand it in those terms. I guess it was just the way that sentence was worded that threw me off.

And the paragraph you quoted from just before Listing 19.8 helps to understand the issue too. It’s still a little weird to think that a variable, function argument, or return value can have a protocol as its type. But maybe I will start to get more comfortable with how that works as I start to use protocols and generics more.

Thanks for your help!

Think of a protocol as a type for a pure interface specification, which doesn’t reveal any implementation details. It is an incomplete type; that is, it can’t be instantiated (Although a default implementation can be provided if desired, still direct instances of it can’t be created.)

A practical protocol example:

protocol Logger {
}

// Default implementation
extension Logger {
    var id : String {
        return "\(type (of:self))"
    }
    
    func log (_ s:String) {
        print ("\(id): \(s)")
    }
    
    func log (_ s1:String, _ s2:String) {
        log ("\(s1): \(s2)")
    }
    
    func error (_ s:String) {
        print ("\(id): error: \(s)")
    }
    func error (_ s1:String, _ s2:String) {
        error ("\(s1): \(s2)")
    }
}

// Create an instance of Logger - will fail
let logger = Logger ()  // Error - Logger is not a complete type

Now let Bar and Foo adopt the Logger protocol - implement the Logger interface.

class Foo {
   ...
}

// Adopt the Logger protocol 
extension Foo: Logger {
   // take default implementation of Logger
}

class Bar {
   ...
}

// Adopt the Logger protocol 
extension Bar: Logger {
   // take default implementation of Logger
}

// Create instances of Foo and Bar
let barLogger : Logger = Bar ()  // Okay
let fooLogger : Logger = Foo ()  // Okay

@ibex10

Thanks for your help, I think that makes sense. I see how to create a variable that has a protocol for its type. As an example, if I declare a variable like this it compiles successfully:

var myLogger: Logger

But I don’t really see how that can be useful. Like, what can you actually do with a variable of protocol type? For example, I can’t instantiate that type of variable like I could using a simple class type. To demonstrate, if I write the following line it will not compile:

var anotherLogger = Logger()

So I guess I cannot yet see the usefulness of giving a protocol type to a variable or return statement. But maybe it’s usefulness is so that you can do the sort of things that were demonstrated in the chapters on generics, protocols, and protocol extensions. I’m just having a little trouble parsing some of those concepts. But I think in time I’ll come to understand it better.

It’s true that a protocol can’t be used in exactly the same way as a type, but it’s still useful to know that a variable conforms to a particular protocol. Going back to Sequence and makeIterator, somewhere in the code for Sequence it’s calling makeIterator. Even though Sequence doesn’t know what type will be returned by makeIterator, it knows that it will conform to IteratorProtocol and so it can perform the functions defined by that protocol on that returned value. Without the protocol, it would have to know the type of the returned value to be able to do anything with it. The flexibility to work with a value even though you don’t know its type is at the heart of what protocols are for. It’s used extensively in writing programs for iOS and macOS.

2 Likes

@JonAult

Thank you, that makes it much more clear. I think what really started my confusion was the phrase, "returns a value of the associated type IteratorProtocol". But I like the other way you put it, "returns a value of a type conforming to IteratorProtocol". That may have been what the author meant, but to me it didn’t come across that way.

Thanks so much for your help, guys!

Here is a more concrete example.

//
//  main.swift
//  ProtocolExample
//
//  Created by prettyfunction.org on 20/4/21.
//

//
// -------------------------------------------------
//
protocol Logger {
    var prefix : String {get}
}

// Default implementation of Logger
extension Logger {
    var prefix : String {
        return "\(type (of:self))"
    }
    
    func log (_ s:String) {
        print ("\(prefix): \(s)")
    }
    
    func log (_ s1:String, _ s2:String) {
        log ("\(s1): \(s2)")
    }
    
    func error (_ s:String) {
        print ("\(prefix): error: \(s)")
    }
    func error (_ s1:String, _ s2:String) {
        error ("\(s1): \(s2)")
    }
}

// -------------------------------------------------
//
protocol Task {
    func run ()
}

//
// -------------------------------------------------
//

class FooTask: Task {
    func run () {
        log ("starting...")
        log ("working...")
        log ("finished.")
    }
}

extension FooTask: Logger {
    // adopt default implementation of Logger
}


class BarTask: Task {
    func run () {
        log ("starting...")
        log ("working...")
        log ("finished.")
    }
}

extension BarTask: Logger {
    // override prefix
    var prefix : String {
        return "\(type (of:self)) the Magnificent"
    }
    // adopt default implementation of Logger for the rest
}

//
// -------------------------------------------------
//
// Driver
//
func getTask () -> Task {
    let tasks = Array <Task> (arrayLiteral: BarTask (), FooTask ())
    let x = Int.random (in: 0..<16)
    return tasks [x % tasks.count]
}

for _ in 0..<3 {
    let task : Task = getTask ()
    task.run ()
}

output may look like this:

BarTask the Magnificent: starting...
BarTask the Magnificent: working...
BarTask the Magnificent: finished.
FooTask: starting...
FooTask: working...
FooTask: finished.
BarTask the Magnificent: starting...
BarTask the Magnificent: working...
BarTask the Magnificent: finished.
Program ended with exit code: 0

@ibex10

Very interesting. I always thought that something like var foo: MyClass meant that foo is of MyClass type. But in your example, let task: Task appears to mean that task conforms to the Task type, instead of meaning that task is of Task type. Which statement would you say is accurate?

  • task is of Task type.
  • task conforms to the Task type.

Or maybe it’s both? Like, task is of Task type and also conforms to the Task protocol type.

I always thought that something like var foo: MyClass meant that foo is of MyClass type.

I think this is what’s causing your difficulties with this concept. Task is not really a type, and task is not a variable of type Task. When someone says “task is of type Task” in reference to a protocol, it’s really just shorthand for what I said earlier, that task is a variable that will be of a type conforming to the Task protocol.

Here’s something you might find interesting. There’s a Swift function type(of:) that returns the type of a variable. Add that to the for loop in the example code @ibex10 posted above:

for _ in 0..<3 {
    let task : Task = getTask ()
    print("task is a \(type(of:task))")
    task.run ()
}

and you get something like

task is a BarTask
BarTask the Magnificent: starting...  
BarTask the Magnificent: working...
BarTask the Magnificent: finished.
task is a FooTask
FooTask: starting...
FooTask: working...
FooTask: finished.
task is a BarTask
BarTask the Magnificent: starting...
BarTask the Magnificent: working...
BarTask the Magnificent: finished.

But, if we create a new variable similar to task, don’t assign it a value, and try to print its type:

var task2: Task
print("\(type(of:task2))")

we get an error:

error: MyPlayground.playground:97:18: error: variable 'task2' used before being initialized

Variables that are defined as adhering to a protocol do not have an inherent type. They cannot have a type until they are assigned a value, at which point they will temporarily adopt the type of whatever value they are assigned:

var task2: Task = BarTask()
print("\(type(of:task2))")
task2 = FooTask()
print("\(type(of:task2))")

produces

BarTask
FooTask

And the error message is not due solely to type(of:) being given an uninitialized variable. If you define a variable to be of a type, you can get its type before it is initialized:

var a: Int
print("\(type(of:a))")

prints

Int

but if you try to print the value of a you’ll get an error that it is uninitialized. You can do that because a has a defined type independent of its value; task and task2 do not, because they are only defined as adhering to a protocol.

Edit: It looks like the error message doesn’t mean what I thought it did. When I tried the following:

var a: FooTask
print("\(type(of:a))")

I got the same uninitialized variable error that I did with task2 even though FooTask is a type. It looks like maybe the reason it works for an uninitialized Int is due to Int being a value type rather than a reference type, which suggests that variables defined as adhering to a protocol are implemented as reference types.

I think the rest still applies, though. You can see the type of task and task2 changing as their values change, and at no point does type(of:) say that either of them are of type Task.

Edit 2: And reading further into the documentation for type(of:), it’s apparently possible for that function to return a protocol name under certain circumstances.

And reading even further, there’s this, from swift.org:

Protocols don’t actually implement any functionality themselves. Nonetheless, you can use protocols as a fully fledged types in your code. Using a protocol as a type is sometimes called an existential type , which comes from the phrase “there exists a type T such that T conforms to the protocol”.

and

Because protocols are types, begin their names with a capital letter (such as FullyNamed and RandomNumberGenerator ) to match the names of other types in Swift (such as Int , String , and Double ).

So there it is, all official: Swift protocols are types.

2 Likes

Going back to the original question, I’m not sure anymore whether there’s a meaningful difference between saying “returns a value of the associated type Iterator” and “returns a value of the associated type IteratorProtocol” since the only thing known about type Iterator is that it adheres to IteratorProtocol. For that matter, I can’t see the usefulness of Sequence defining Iterator as an associated type in the first place; why not just substitute IteratorProtocol everywhere Iterator is used? They’re both types.

Edit: OK, I think I see. When you define something as being of type Sequence, you are also defining a particular Iterator type that will operate on sequences of that specific Element. Sequences of different Element types will have their own unique associated iterators and those iterators are not interchangeable even though they all adhere to InteratorProtocol. You can’t use the iterator for a sequence of characters to iterate through a sequence of integers, for example.

If that’s correct, then the sentence preceding listing 21.17 is incorrect. It should say “returns a value of the associated type Iterator”, because Iterator and IteratorProtocol are not interchangeable. It’s not enough for makeIterator to return a value that conforms to IteratorProtocol, the return value also has to operate on the appropriate Element type.

1 Like

@JonAult

Wow. Thanks for sharing that journey of your research. You came upon some really interesting observations. That makes a lot of things about protocol usage more clear for me.

I also agree with your conclusion that the sentence “which returns a value of the associated type IteratorProtocol” should be changed to “which returns a value of the associated type Iterator”. As you say, those terms don’t seem to be interchangeable here.

What do you think, @ibex10 ?

Hey y’all! Mikey here, I wrote the words in question.

First off, I love this thread. The discussion in here is important and useful and extremely, extremely cool.

To weigh in on the question at hand, I consider the answer to be: yes, this is a typo. Indeed it should read “…returns a value of the associated type Iterator”. Thank you for catching this! We will fix it for the next printing.

To expand on the reasoning, I absolutely agree that protocols are types, even though they cannot be used in all of the same ways that structs and classes can. However, the type name following the associatedtype keyword in a protocol declaration is, conceptually, used in the same way that a generic type name like T is used in a generic function.

There are differences between associated types and generic placeholder types at the implementation level (which is why they have different syntax; I’d be happy to expand on that if you’re interested), but in our mental model, they serve the same purpose: to define a placeholder for some other concrete type (struct or class) that will be filled in by other code. In the case of this exercise, the StackIterator.

As with generic functions and data structures, we can apply a type constraint to an associatedtype, and here we do: we require that the Iterator placeholder must be filled in with a concrete type that conforms to IteratorProtocol.

But that is all side information. At the end of the day, we have declared an associatedtype placeholder called Iterator and that placeholder is substituted with your concrete type StackIterator at compile time, and compilation succeeds because the IteratorProtocol type constraint is satisfied.

So it is the Iterator type, or more exactly, your concrete type that substitutes Iterator at compile time (StackIterator), that the makeIterator() method must return in your implementation.

Does that help?

1 Like

@MikeyWard

Okay, I think I finally get it. I think part of the problem is that in Listing 21.17 there is no typealias statement to show explicitly how Stack<Element> will conform to Sequence. The associated types are being inferred there.

It might be helpful to have an intermediate example similar to Listing 21.16 where we first see the typealias being declared explicitly before we remove it. I realize that would be a second demonstration of the concept, and there is also a sentence below Listing 21.17 that briefly mentions this. But personally, I find the topic of generics to be especially difficult to grasp (at least for a beginner). So a little extra hand-holding might be helpful.

The Big Nerd Ranch books are the best programming guides I have encountered, and it’s great to see an author responsive enough to weigh in about typos and things in the forums. I’m very grateful to everyone who replied here. I’m glad that we were able to get confirmation on this matter.

All of this discussion forced me to revisit parts of the Generics chapter that I didn’t fully grasp the first time around (actually this is my second time around, since I went through the previous edition of this book a few years ago). There were some parts of this chapter that I thought I understood, but the chapter left me feeling a bit fuzzy about this topic overall.

Upon revisiting, it turns out that I had not actually grasped certain concepts after all. A few days of rehashing the material with you folks, some nights of sleeping on it and some more time spent messing around in playgrounds has really helped to illuminate my understanding about this topic.

It would be interesting to hear about the implementation differences between associated types and generic placeholder types. I guess if you declared generic protocol types the same way you declared other generic types, you would have declarations like this:

struct Stack<Element>: IteratorProtocol<Element> {
}

Which I guess might look a little weird.

Totally tangential side question… Has the Big Nerd Ranch ever considered making a book about the basics of video game programming? There are a lot of tutorials already out there about how to make simple 2D games with SpriteKit. But I have found SpriteKit to be lacking and I’m craving a more fundamental understanding about how game engines work. I bet BNR could do a great job teaching these concepts, perhaps by showing how to build a simple 2D game engine from scratch in Swift.

Anyway, I would find such a book fascinating from an educational perspective. Indie game development is my ultimate programming goal. But if one wants to stick with Swift, there seems to be very little instructional material out there that is both comprehensive and of high quality. Maybe I will eventually be forced to take a closer look at Unity or Unreal Engine. (And it would be great to have some BNR-quality instruction in that area too!)

Does anyone know some good intro-level books about game engines or game programming that they can recommend?