Swift - Sequence - Why I'm not required to add typealiases for Element and Iterator?

Viewed 108

I'm learning Swift and cannot understand why I can just declare

class MultiSet<T: Hashable> : Swift.Sequence {
    var data = [T]()
    
    func makeIterator() -> Array<T>.Iterator {
        return data.makeIterator()
    }
}

and that would work. However, if I copy the Sequence declaration and write

class MultiSet<T: Hashable> : MySequence {
    var data = [T]()
    
    func makeIterator() -> Array<T>.Iterator {
        return data.makeIterator()
    }
}

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

I'm getting errors that I need to explicitly write protocol stubs:

    typealias Element = <#type#>
    typealias Iterator = <#type#>

How it's automatically inferred?

Thanks!

1 Answers

It's because the constraint you add in the associatedtype declaration is just a constraint, and doesn't actually resolve the type.

To be able to resolve the associatedtype you need some kind of reference in the protocol (or the implementation) to that associatedtype.

In your example, your implementation only "resolve" the Iterator associatedtype:

public protocol MySequence {
    func makeIterator() -> Self.Iterator
}

class MultiSet<T: Hashable> : Swift.Sequence {
    func makeIterator() -> Array<T>.Iterator {
        return data.makeIterator()
    }
}

Because you "replaced" Iterator with Array<T>.Iterator in the implementation, Swift understands how Iterator should be resolved. But At that point Element is still not resolved.

Note: Xcode does ask for both Element and Iterator type aliases in the implementation, but Element is the only one actually missing. If you add

typealias Element = T

In MultiSet it fixes the error.

If you want Swift to be able to resolve Element you will need the same kind of "reference", either in your implementation of MultiSet or directly inside your protocol with an extension.

For example:

public protocol MySequence {
    associatedtype Element where Self.Element == Self.Iterator.Element
    associatedtype Iterator : IteratorProtocol
    func makeIterator() -> Iterator

    func foobar() -> Element? // added a reference to Element
}

extension MySequence {
    func foobar() -> Iterator.Element? {
        nil
    }
}

struct MultiSet<T>: MySequence {
    var data = [T]()

    func makeIterator() -> Array<T>.Iterator {
        return data.makeIterator()
    }
}

This will allow Swift to resolve Element to Iterator.Element.

Note: You can define foobar inside MultiSet without a default implementation in an extension. I used this example because it is how it's done inside Sequence.

If you take a look at the Sequence implementation (Source code) you will notice the _customContainsEquatableElement function (that references Element), with a default implementation (which resolve Element to Iterator.Element)

public protocol Sequence {
  func _customContainsEquatableElement(
    _ element: Element
  ) -> Bool?
}

// [...]

extension Sequence {
  public func _customContainsEquatableElement(
    _ element: Iterator.Element
  ) -> Bool? {
    return nil
  }
}

This is why when implementing Sequence you don't have to explicitly resolve Element, it's implicitly resolved to Iterator.Element.

Related