Search code examples
swiftgenericsswift-protocolstype-erasureassociated-types

Swift couldn't infer generic type when I was trying to provide my own implementation of AnySequence


The problem is when one protocol depends on another through its associated type, compiler isn't able to infer generic types.

So, I was playing around with Swift's type erasure technique trying to become familiar with its idea. Basically it'd been pretty understandable until I got to Sequence protocol. It's known that it has an associated type - Iterator, which conforms IteratorProtocol. That said, I've been trying to achieve similar behavior in my own implementation. That's what I've done:

final class CustomAnySequence<Element>: Sequence {

    class CustomAnyIterator<Element>: IteratorProtocol {
        private let _next: () -> Element?

        init<I: IteratorProtocol>(_ iterator: I) where I.Element == Element {
            var iterator = iterator
            _next = { iterator.next() }
        }

        func next() -> Element? {
            return _next()
        }
    }

    typealias Iterator = CustomAnyIterator<Element>
    typealias Element = Iterator.Element


    private let _makeIterator: () -> Iterator


    init<S: Sequence>(_ sequence: S) where S.Iterator == Iterator {
        _makeIterator = sequence.makeIterator
    }

    func makeIterator() -> Iterator {
        return _makeIterator()
    }

}

let sequence = CustomAnySequence([1, 2, 3])

So, the last line gives the following error: Generic parameter 'Element' could not be inferred.

Then if I try to fix it by explicitly specifying Element type:

let sequence = CustomAnySequence<Int>([1, 2, 3])

it's not making it better. The next Xcode complaint is: Generic parameter 'S' could not be inferred.

So is there my fault, or it's just too much overhead for Swift's type inference?

Actually, I've run into another possible implementation - it's using private subclass wrapping. I don't really like it (that's why I was trying to do it on my own) because there are "fatal-error-must-be-subclassed" methods in superclass's implementations, which don't contribute to clean code. Also, I'm not sure how I could implement this functionality by the initializer of CustomAnySequence (I've only found it possible by making a static method). Despite all that, that's the code:

class CustomAnySequence<Element>: Sequence {
    class Iterator: IteratorProtocol {
        func next() -> Element? {
            fatalError("Must be overriden")
        }
    }

    func makeIterator() -> Iterator {
        fatalError("Must be overriden")
    }
}

private final class CustomAnySequenceImplementation<S: Sequence>: CustomAnySequence<S.Element> {
    final class IteratorImplementation: Iterator {
        var wrapped: S.Iterator

        init(_ wrapped: S.Iterator) {
            self.wrapped = wrapped
        }

        override func next() -> S.Element? {
            return wrapped.next()
        }
    }

    var sequence: S

    init(_ sequence: S) {
        self.sequence = sequence
    }

    override func makeIterator() -> IteratorImplementation {
        return IteratorImplementation(sequence.makeIterator())
    }
}


extension CustomAnySequence {
    static func make<S: Sequence>(_ sequence: S) -> CustomAnySequence<Element> where S.Element == Element {
        return CustomAnySequenceImplementation<S>(sequence)
    }
}

func printInts(_ sequence: CustomAnySequence<Int>) {
    for element in sequence {
        print(element)
    }
}


printInts(CustomAnySequence.make([1, 2, 3]))
printInts(CustomAnySequence.make(Set([4, 5, 6])))

It actually does work, but it looks a bit like a boilerplate. At least, if you realize how to improve it by using an initializer, please let me know. Thank you in advance!


Solution

  • The problem with the first implementation is that

    let sequence = CustomAnySequence([1, 2, 3])
    

    does not satisfy the constraint in

    init<S: Sequence>(_ sequence: S) where S.Iterator == Iterator 
    

    [1, 2, 3] is a sequence, but its iterator type is not your CustomAnyIterator. What you really want is to pass a sequence with the same element type, not the same iterator type:

    init<S: Sequence>(_ sequence: S) where S.Element == Element 
    

    and pass sequence.makeIterator() to the init method of CustomAnyIterator.

    Note also the inner class can inherit the Element type placeholder from the outer class, and that the type aliases are not really needed.

    final class CustomAnySequence<Element>: Sequence {
    
        class CustomAnyIterator: IteratorProtocol {
            private let _next: () -> Element?
    
            init<I: IteratorProtocol>(_ iterator: I) where I.Element == Element {
                var iterator = iterator
                _next = { iterator.next() }
            }
    
            func next() -> Element? {
                return _next()
            }
        }
    
        private let _makeIterator: () -> CustomAnyIterator
    
        init<S: Sequence>(_ sequence: S) where S.Element == Element {
            _makeIterator = { CustomAnyIterator(sequence.makeIterator()) }
        }
    
        func makeIterator() -> CustomAnyIterator {
            return _makeIterator()
        }
    }
    

    You may also consider to use a struct instead of a class.