Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you specify a return type in Swift as being any collection of a particular type?

I have implemented a custom queue object that I would like to use to store previous items in a controller. I would like to use this type as a private variable in my controller, and only expose it to the outside as simply a CollectionType-compliant object, so that clients could iterate or index the object without knowing any class-specific details, such as a clear() function.

Swift protocols can not be generic, so unfortunately I can't simply define a getter to return a CollectionOf<Type>. I have implemented this behavior using the following abstract base class and subclassing my collection from it, but I was hoping that there might be a more Swift-y and builtin method to achieve this, which hopefully would also not require subclassing:

class AnyCollectionOf<MemberT, IndexT: ForwardIndexType>: CollectionType {
    // Sequence Type
    func generate() -> GeneratorOf<MemberT> {
        fatalError("must override")
    }

    // Collection Type
    typealias Index = IndexT
    typealias Element = MemberT

    subscript (index: Index) -> Element {
        get {
            fatalError("must override")
        }
    }

    var startIndex: Index {
        get {
            fatalError("must override")
        }
    }

    var endIndex: Index {
        get {
            fatalError("must override")
        }
    }
}
like image 254
gntskn Avatar asked Nov 27 '14 01:11

gntskn


People also ask

How do you return a type in Swift?

You write an optional tuple return type by placing a question mark after the tuple type's closing parenthesis, such as (Int, Int)? or (String, Int, Bool)? . An optional tuple type such as (Int, Int)? is different from a tuple that contains optional types such as (Int?, Int?) .

CAN protocol functions have return types?

Using a protocol type as the return type for a function gives you the flexibility to return any type that conforms to the protocol. However, the cost of that flexibility is that some operations aren't possible on the returned values.

What is never return type when to use it over void in Swift?

The Never return type is a special one in Swift, and tells the compiler that execution will never return when this function is called. It's used by Swift's fatalError() and preconditionFailure() functions, both of which cause your app to crash if they are called.

What is type () in Swift?

In Swift, there are two kinds of types: named types and compound types. A named type is a type that can be given a particular name when it's defined. Named types include classes, structures, enumerations, and protocols. For example, instances of a user-defined class named MyClass have the type MyClass .


1 Answers

The answer to your headline question is, unfortunately, no: there is no way to jury-rig a CollectionType as a stand-alone type that can be used as a variable or return type.

Protocols like SequenceType and CollectionType require the classes that implement them to provide typealiases to fill in the implementation details such as the element type, as you've done above. Once a protocol adds those requirements, it can never again be used as a stand-alone type. You can only declare interfaces in terms of specific classes that conform to it. (If you've tried to work around this, you may remember seeing not-very-helpful compiler errors about "associated type requirements".)

That's the basic reason why you can't write

func countItems(collection: CollectionType) -> Int { ... }

but must write instead

func countItems<T: CollectionType>(collection: T) -> Int { ... }

The latter form ensures that the compiler has access to the actual type of object (T) that's implementing the CollectionType protocol.

However, there may still be a cleaner implementation of what you're trying to do if you think in terms of encapsulation rather than inheritance. You can use a simple wrapper to block access to everything but the core CollectionType methods:

struct ShieldedCollection<UCT: CollectionType> : CollectionType
{
    private var underlying: UCT

    func generate() -> UCT.Generator { return underlying.generate() }
    subscript(index: UCT.Index) -> UCT.Generator.Element { return underlying[index] }
    var startIndex: UCT.Index { return underlying.startIndex }
    var endIndex: UCT.Index { return underlying.endIndex }
}

var foo = [1, 2, 3]
var shieldedFoo = ShieldedCollection(underlying: foo)

(Here, UCT = "underlying collection type".)

ShieldedCollection still has all the usual typealiases to be a CollectionType, but since those can be inferred from context, you don't have to specify them explicitly.

The flaw with this generic approach, and unfortunately it's a rather major one, is that the underlying type still leaks out into the API. The type of shieldedFoo in the example above is

ShieldedCollection<Array<Int>>

Since your underlying collection is a custom object, its name would still potentially leak in the API, even if the class itself was not directly accessible by clients. Note that this isn't a functional problem, though, since it shouldn't be possible to access the underlying object through the ShieldedCollection wrapper. Furthermore, consumers will never have to write the type themselves - they can just use the result of previousItems() as a CollectionType and the compiler will disentangle everything.

If you're really intent on obscuring all mention of the underlying collection type, you can write a task-specific analog of the wrapper above by moving the UCT definition inside ShieldedCollection:

struct ShieldedCollection<T> : CollectionType    // Changed!
{
    typealias UCT = [T]                          // Added!

    private var underlying: UCT                  // Everything else identical

    func generate() -> UCT.Generator { return underlying.generate() }
    subscript(index: UCT.Index) -> UCT.Generator.Element { return underlying[index] }
    var startIndex: UCT.Index { return underlying.startIndex }
    var endIndex: UCT.Index { return underlying.endIndex }
}

var foo = [1, 2, 3]
var shieldedFoo = ShieldedCollection(underlying: foo)

Here you make the return type tidy by giving up full generality -- this version of ShieldedCollection will work only with underlying CollectionTypes that are Arrays. (Of course, you would just substitute your own custom collection type in the typealias for UCT.)

like image 130
GSnyder Avatar answered Sep 24 '22 19:09

GSnyder