2

tl;dr

Here's the best tl;dr I can come up with for those that don't want to read the longer explanation. If you haven't used a statically typed language with generic collection interfaces, this question is not for you. So here goes:

Since we can't create a property of type SequenceType in Swift, what do we do instead if we want to hide the underlying type of our sequence? Also, how do we do this for mutable collections?

E.g., this doesn't work:

class FrogBox {
    var _frogs = [Frog]()
    var frogs: SequenceType {
       return _frogs
    }
}

So what do we do instead if we want to hide the fact that we are using an array, especially since we don't want the user to directly modify the array?

The Long Explanation

In a not-so-distant phase of my career, I wrote lots of C#, though Objective-C and Ruby are much more common now. In C#, if I want to expose an iterable collection as a property of a class, I'd declare as IEnumerable<T>, e.g.,

class FrogBox {
    private readonly ArrayList<Frog> _frogs = new ArrayList<Frog>();
    public IEnumerable<Frog> frogs { get { return _frogs; } }
}

In Objective-C, on the other hand, the usual way is just to use the Swiss Army knives NSArray or NSMutableArray for everything, e.g.,

@interface FrogBox
@property (nonatomic, readonly) NSArray *frogs;
@end

@implementation FrogBox {
    NSMutableArray *_frogs;
}
@dynamic frogs;
- (instancetype)init {
    self = [super init];
    if (self) {
        _frogs = [NSMutableArray array];
    }
    return self;
}
- (NSArray*)frogs {
    return _frogs;
}
@end

In Swift, we have the opportunity to do something more along the lines of C#, which I prefer because it allows us to encapsulate how the frogs are actually stored. Since our (fairly useless) FrogBox only allows us to iterate the frogs, why do we care how they are stored?

This turns out to be a bit harder to achieve in Swift. Some protocols can only be used as generic type constraints because they have associated type constraints (using typealias) or use Self in their definition. SequenceType does both, so we can't say var frogs: SequenceType in our implementation of FrogBox in Swift.

This leaves us with two choices. First, we could simply abandon encapsulation and use an array, in the manner of Objective-C. Or we could use SequenceOf<T> as follows:

class FrogBox {
    private var _frogs = [Frog]()
    var frogs: SequenceOf<Frog> {
        return SequenceOf<Frog>(_frogs)
    }
}

The documentation for SequenceOf<T> says that it "[f]orwards operations to an arbitrary underlying sequence with the same Element type, hiding the specifics of the underlying sequence type.". This is great, but what's this about an "arbitrary underlying sequence"? Does this mean my elements are copied to an arbitrary underlying sequence? What's the overhead of that? Or is it saying that the "arbitrary underlying sequence" is the one I give it? (I think the latter is more likely, though they should have said the "given underlying sequence".) SequenceOf<T> may be the best answer, but I hesitate to use it until I get clarification. I could roll my own without too much trouble, but this wouldn't be standard.

So, what is the best practice in Swift? Stick with the "Swift Army Knife" that is Array<T> (and risk exposing a writeable interface for something that should just be a sequence) or use SequenceOf<T> or perhaps some other methodology I hadn't thought of? Also, how do we go above and beyond simple sequences while still hiding the underlying implementation? There appears to be no CollectionOf<T> for indexable collections. Nor is there any equivalent of C#'s IList<T> that I'm aware of, to encapsulate a list that can be modified. (Correction: It appears that Swift has MutableCollectionType to which Array<T> conforms. However, there is no MutableCollectionOf<T>, and MutableCollectionType is a pretty anemic interface.)

How to achieve all of this in Swift?

Gregory Higley
  • 15,923
  • 9
  • 67
  • 96
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/65447/discussion-on-question-by-gregory-higley-best-practices-to-achieve-encapsulation). – Andrew Barber Nov 22 '14 at 22:11

1 Answers1

2

Your examples from various languages don't do the same things, so getting at what you want depends on which behavior you're after.


You want to allow clients of your class to see the collection of frogs, but not mutate the collection? (A la your ObjC example that exposes an NSArray that's backed by an `NSMutableArray.) There's an access modifier for that:

public class FrogBox {
    private(set) var frogs = [Frog]()
}

Mutating a collection value is semantically the same as setting it—that's why let vs var control mutability. When you use private(set), what effectively happens is that your class sees frogs as a var while the rest of the world sees it as a let.

This is not unlike your ObjC example in that the mutability is hidden in the private interface. Unlike ObjC, though, Swift enforces the private interface at run time.


You want to accomplish the above goal and hide the type of the underlying collection? Then your SequenceOf<T> solution is the one to go with.

As @Rintaro mentioned in chat, SequenceOf essentially passes through to the generate() function of whatever collection it's initialized with. That is, where the header-doc comments say "arbitrary underlying sequence", it means the "arbitrary" part is your choice. (You're right, "given underlying sequence" might be a better summary.)

You can see this for yourself by testing with an array of reference types and modifying members from the SequenceOf accessor:

class Frog {
    var name: String
    init(name: String) { self.name = name }
}
class FrogBox: Printable {
    private var _frogs = [Frog]()
    var frogs: SequenceOf<Frog> {
        return SequenceOf<Frog>(_frogs)
    }
    init() {
        _frogs.append(Frog(name: "Murky"))
        _frogs.append(Frog(name: "Lurky"))
    }
    func change() {
        _frogs[0].name = "Gurky"
    }
    var description: String {
        return join(", ", _frogs.map({ frog in frog.name }))
    }
}

let box = FrogBox()
// description -> "Murky, Lurky"
for frog in box.frogs { frog.name = "Gurky" }
// description -> "Gurky, Gurky"

If constructing a SequenceOf copied the underlying sequence, mutating one of its members wouldn't change the original _frogs array in the FrogBox.

rickster
  • 124,678
  • 26
  • 272
  • 326