[swift-evolution] [Review] SE-0025 Scoped Access Level

plx plxswift at icloud.com
Sat Feb 27 09:27:30 CST 2016


> What is your evaluation of the proposal?
Short form: the problem addressed is very real and IMHO very significant; the proposed solution is *useful* but on reflection seems to illustrate this isn’t a problem that can be *entirely* addressed just by adding another scope (or scope(s)). I think I agree with what I think Drew has been saying that the visibility model itself may be not quite ideal (it isn’t broken or anything). 

I’m also a little unsure how useful `local` will be in the context of the rest of the language at this time; it seems like to really come into its own we’d also want e.g. to be able to split up the definition of a type across multiple extensions within the same module.

Longer form: within Swift’s existing access-control approach, if you want to keep things private—as opposed to merely internal—it’s easy to wind up with numerous closely-related types, all living in the same file. The only sensible visibility for most of the aspects of most of the types is `private`, but because these are all living in the same file we don’t actually get any effective "cross-type” access-protection within that file.

So e.g. for a custom type implementing `CollectionType`, you can have the type itself, an associated index type, and perhaps also an associated generator type; if instead of “is-a `CollectionType`” you instead opt for “has-multiple `CollectionType` views”, then you can wind up with the base type + 2-3 closely-related types per such view (the collection type, its index, and perhaps also the generator).

The standard-library `String` is an example, where it’s not itself a collection but has 4 “views”, each of which defines an index, and one of which also adds a custom generator.

I don’t have a short example I can share, but I can describe one: at one point I wrote an “accelerated 2D point array”, which was effectively an array of `CGPoint`, but in struct-of-arrays style (e.g. separate arrays of `x` and `y` coordinates). The tricky part is it had a delicate internal state, since the actual storage looked like this (many details elided here) :

@implementation PointArray {
  float *_bufferA;
  float *_bufferB;
  float *_bufferC;
}

@property(nonatomic, assign) PointArrayState state;

@end

…wherein *which* buffer was for `x` or `y` varied over time (and was tracked via that `state` field). The reason for this design is many of the array-level operations were implemented using functions from Accelerate, which in turn often benefit from being done “out-of-place” (and thus e.g. to translate each point by dx,dy, if you started out with `bufferA` having the x-coordinates and `bufferB` having the y-coordinates, you might wind up with `bufferC` holding the updated x-coordinates and `bufferA` holding the updated y-coordinates, along with `state` updated to reflect that updated arrangement).

This is code that would arguably benefit from being migrated to Swift at some point. As it is in Objective-C, it’s implemented as part of a larger family of classes, and it’s easy to tightly control visibility: there’s public headers, there’s private headers imported by subclasses, and there’s methods defined in .m files that are difficult to call accidentally from anywhere else. 

In Swift the design would change a bit — a lot of methods that in Objective-C are effectively “sort if necessary, then call this block on each point” would get migrated to “view structs” that implement `CollectionType` — but I get nervous thinking through the implementation because it seems at least a little awkward to structure the internal API in a robust way, while also having so many types all defined in the same file.

I’m not entirely sold that `local` is the ideal solution, but it’s definitely addressing an IMHO significant problem.

What does concern me about `local` is that, at present, it would seemingly force an unnatural code organization at times; the norm is typically to put each adopted protocol into its own extension, but this means that any stored fields must either be `private` — and thus not protected by `local` — or you have to implement a lot of things from within the main definition (not idiomatic).

Even then, consider a simple case like this:

public struct SomeCustomIndex<T> {
  local let position: Position
}

public func ==<T>(lhs: SomeViewIndex<T>, rhs: SomeViewIndex<T>) -> Bool {
  return lhs.position == rhs.position // oops!
}

…(in this case it’s hard to see what harm you could do with `private let position`, but it illustrates a general limit to the utility of `local` declarations).

> Is the problem being addressed significant enough to warrant a change to Swift?
Yes, VERY MUCH SO.

> Does this proposal fit well with the feel and direction of Swift?
I’m not sure. I feel like there should be some better approach to visibility that solves this problem and is overall more “Swift”, but I don’t know what it is.

> If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
I think the closest analogy here is that "private + local" are trying to get Swift to the same place that, say, “protected + friend” would get it, but along a different route.

The general problem being solved is that Swift’s somewhat-unique approach to `private` is unsatisfactory for some cases—IMHO in particular for the “family of related types” scenario—and `local` adds another tool to solve it.

Although I’m well aware of the arguments against “protected (+ friend)”, I’d point out that those constructs are IMHO the *usual* approach to solving these same issues.


> How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal, participated in original discussion, read the current discussion.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160227/80fd6c81/attachment.html>


More information about the swift-evolution mailing list