[Review] SE-0025 Scoped Access Level

Threading is one especially pernicious case. If I have an ivar that is only safe for access from one thread, I *need* compiler enforcement. I *need* a guarantee that this ivar is only accessed through public interface methods that can be audited to be threadsafe. Simply a doccomment that says "bad programmer, don't do it" is not enough.

I’m unsure what you are imagining here - compiler protection of class details from within a closure completion handler that is also part of your class definition? I believe you would only get this with this local scope proposal if you structured your code such that callback blocks were functions outside your type definition.

If you are talking about access or modification of the inner state of a class and a manual audit of safety, that audit is of the file and not of the type or extension. I’m unsure if your concern is of having to split code into multiple files for safety, or that there is not a way to split code into multiple files to achieve safety in some particular scenario.

This is not even a matter of "artistic choice" of whether or not I want to follow "one file per class". I can achieve thread safety with "private" ivars and "one file per class", but if my class is UITableViewCellContentView (which is an implementation detail that should be hidden even to most of UIKit) I am now forced to expose that implementation detail to my entire team.

This places me in the unconscionable situation of choosing between whether I have thread safety or encapsulation, between whether my coworker will accidentally create a threading bug or accidentally use a class they ought not to use and I am unable to appropriately hide.

I’m not quite sure what you mean here - exposing that your class is a subclass of UITableViewCellContentView? Or that the rest of your team needs to code in different files from your class in order to maintain encapsulation?

It may be that I’m unfamiliar with UITableViewCellContentView (as it is not documented).

<snip>

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

I've followed this from the earliest discussions. I've rethought my position somewhat in response to the growing uncertainty about dropping the NS prefix, which I think exposes some very real problems with visibility in Swift.

As that situation has developed, I no longer believe this proposal goes far enough. But it does go somewhere, and we should not stay where we are.

I don’t understand how an access control proposal pertains to changes in the Foundation public API. What is the ideal end state in your mind, and in what way is this a step toward that?

-DW

···

On Feb 26, 2016, at 8:44 PM, Drew Crawford via swift-evolution <swift-evolution@swift.org> wrote:

I'm curious if you could make the example more concrete and provide actual code that would benefit? One of the tough things about evaluating both SE-0025 and SE-0026 is that the proposals themselves contain very basic examples that don't sell the concepts.

···

On Feb 26, 2016, at 10:21 PM, Nate Cook via swift-evolution <swift-evolution@swift.org> wrote:

For example, one algorithm for rotating a collection uses an "unguarded" rotate as one of its steps, which is optimized to rotate a portion of the collection without bothering to check the inputs. It makes sense to extract this bit of the full rotation algorithm into a separate function, but it's unsafe to call unless very specific requirements are met that are unlikely outside the expected context. Being able to specify a local access level for the unguarded rotation would make this safer.

--
Stephen

+1 for the local scope.

-Van

···

On Fri, Feb 26, 2016 at 10:44 PM, Dany St-Amant via swift-evolution < swift-evolution@swift.org> wrote:

Le 26 févr. 2016 à 14:05, Douglas Gregor via swift-evolution < > swift-evolution@swift.org> a écrit :

The review of SE-0025 “Scoped Access Level" begins now and runs through
March 3, 2016. The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md

One good thing which is not fully highlighted in the proposal is when
using nested classes, one can protect the guts of the inner class from the
containing class. For completeness the local keyword would have to be
useable to create local setter as well.

class outer {
    class counter {
        local(set) var count: Int = 0 // Cannot set from outer
        func incr() { count += 1 }
    }
    var data = counter()
    func somework() { data.incr() }
}

Dany

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’m unsure what you are imagining here - compiler protection of class details from within a closure completion handler that is also part of your class definition?

What i mean here is simply

class Foo {
  ///it is undefined behavior to access this variable except from specialQueue
       private var _specialQueueOnly = 0

  public var specialQueueOnly: Int {
    var i: I! = nil
    dispatch_sync(specialQueue) {
      i = _specialQueueOnly
    }
    return i
  }
}

The lynchpin of this defensive programming technique is that only these 12 lines of code have any risk of a threading bug, and we can trivially inspect the 12 lines. So our safety hangs on two tentpegs:

1. That "private" in "private var _specialQueue" is compiler enforcement against other files trying to access this ivar
2. That the file itself is 12 (or other minimal number of) lines and trivially inspectable.

Should we violate any of these constraints, we lose our safety.

It may be that I’m unfamiliar with UITableViewCellContentView (as it is not documented).

Well, that is kind of the point: Objective-C's access control worked, and prevented you from knowing about this class.

But to provide a more accessible illustration, consider the case where we have some motivation to hide Foo from the rest of our framework[/module/target/application/executable/library]. This is more likely to happen in a UIKit-sized project, where there are hundreds of public classes, and probably thousands of "internal" ones, and a typical class has motivation to touch 5 or 6 other classes, of the thousands that may be available.

In Swift, each internal class is visible to every other class. But that is not especially workable at UIKit scale; if every class can potentially reach every other class we are in for an adventure when one of the hundred developers on your team decides that some UITableView implementation detail you've never heard of should be accessing some UILocalNotification detail you've also never heard of. So we need some kind of "fencing" within a large framework to make good neighbors.

This is solved very easily: we can group several related classes into one file, and some of the classes are private. Many ordinary people today group related classes into a file as a fencing mechanism even not at UIKit-scale. So a file can access all of its own classes, but not all the classes of other files. That creates the "fence", and now your coworker cannot draw a line between some UITableView secret class and some UILocalNotification secret class, and your desk will not be dented from the impact of your forehead.

The problem now is that while fixing this situation we have broken one of our safety tentpegs. We earlier required that Foo.swift be only 12 lines for thread safety, but now Foo is contained in a larger file so as to create a fence. So we can solve one of these problems or the other one, but never both at the same time.

"local" effectively resolves this dilemma, because if our _specialQueueOnly variable is local, then it is not the /file/ which must be kept to 12 lines, but the /scope/. So we could group an unlimited number of classes in Foo.swift, with no loss of confidence for our thread safety.

A better approach might be to realize that if global scope, target scope, and file scope do not solve the visibility problem, perhaps yet another scope will not totally solve the problem either. I fully expect Apple will need a "vendor" scope for example (so that UIKit and CoreAnimation, two public frameworks, can still have private APIs between them), and I bet there are many more kinds of scopes that have not yet occurred to me.

Behind that realization lies the Rust system, which divorces visibility from these arbitrary scopes that we seem to be struggling to fit into. But that proposal isn't before us, and this one is. I prefer going somewhere to staying here.

Drew

···

On Feb 26, 2016, at 10:34 PM, David Waite <david@alkaline-solutions.com> wrote:

On Feb 26, 2016, at 8:44 PM, Drew Crawford via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Threading is one especially pernicious case. If I have an ivar that is only safe for access from one thread, I *need* compiler enforcement. I *need* a guarantee that this ivar is only accessed through public interface methods that can be audited to be threadsafe. Simply a doccomment that says "bad programmer, don't do it" is not enough.

I’m unsure what you are imagining here - compiler protection of class details from within a closure completion handler that is also part of your class definition? I believe you would only get this with this local scope proposal if you structured your code such that callback blocks were functions outside your type definition.

If you are talking about access or modification of the inner state of a class and a manual audit of safety, that audit is of the file and not of the type or extension. I’m unsure if your concern is of having to split code into multiple files for safety, or that there is not a way to split code into multiple files to achieve safety in some particular scenario.

This is not even a matter of "artistic choice" of whether or not I want to follow "one file per class". I can achieve thread safety with "private" ivars and "one file per class", but if my class is UITableViewCellContentView (which is an implementation detail that should be hidden even to most of UIKit) I am now forced to expose that implementation detail to my entire team.

This places me in the unconscionable situation of choosing between whether I have thread safety or encapsulation, between whether my coworker will accidentally create a threading bug or accidentally use a class they ought not to use and I am unable to appropriately hide.

I’m not quite sure what you mean here - exposing that your class is a subclass of UITableViewCellContentView? Or that the rest of your team needs to code in different files from your class in order to maintain encapsulation?

It may be that I’m unfamiliar with UITableViewCellContentView (as it is not documented).

<snip>

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

I've followed this from the earliest discussions. I've rethought my position somewhat in response to the growing uncertainty about dropping the NS prefix, which I think exposes some very real problems with visibility in Swift.

As that situation has developed, I no longer believe this proposal goes far enough. But it does go somewhere, and we should not stay where we are.

I don’t understand how an access control proposal pertains to changes in the Foundation public API. What is the ideal end state in your mind, and in what way is this a step toward that?

-DW

By the same logic, the comment alone is enough, the private keyword isn't
necessary. The benefit of scoped is that the intent would be enforceable by
the compiler.

···

On Fri, Feb 26, 2016 at 6:48 PM Joseph Lord via swift-evolution < swift-evolution@swift.org> wrote:

On Feb 26, 2016, at 7:05 PM, Douglas Gregor via swift-evolution < > swift-evolution@swift.org> wrote:

The review of SE-0025 “Scoped Access Level" begins now and runs through
March 3, 2016. The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md

-0.1
There is nothing wrong with this proposal but I just don't see the value
over private (plus a little doc comment if absolutely necessary).

Downside is more language to learn / encounter and complexity to maintain.

I've read the proposal but not the preceding discussion.

Joseph
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Just a note:

The emerging pattern in Swift development is to keep type definitions short and focused, and then add additional functionality with extensions.

The remaining big problem for me in using extensions a lot is the fact that you can’t define stored properties in them. This means I rarely split classes into core and extensions as a means of grouping related stuff, because I want to visually group related methods *and* properties.

I’m not 100% convinced on the proposal just yet, but I think I like the idea. However, without the capability to extend a class with an additional property, the usefulness of `local` is greatly limited.

— Radek

···

On 27 Feb 2016, at 07:46, Nicola Salmoria via swift-evolution <swift-evolution@swift.org> wrote:

What is your evaluation of the proposal?

+1

Is the problem being addressed significant enough to warrant a change to Swift?

I think it addresses a significant omission in the language which deserves to be filled.

Does this proposal fit well with the feel and direction of Swift?

I think it does.

The emerging pattern in Swift development is to keep type definitions short and focused, and then add additional functionality with extensions.

Extensions therefore become the basic blocks of code in a type. Sometimes they need to contain implementation details which are only relevant to the specific functionality implemented in the extension, and don’t need to bubble up to the surface of the whole type.

Normally when we work in a block between curly braces, everything we declare is local.
When we work between the curly braces of an extension block, however, we don’t have any way to achieve the same behaviour: everything we declare needs to be shared at a minimum with the whole file.

Adding the ‘local’ access level merely reinstates what we take for granted in other parts of the code. It means that as a code maintainer, when I see a ‘local’ entity in an extension I only need to check how it is used inside that extension, rather than in the whole file.

This will become even more important if in the future it will be possible to declare properties inside extensions.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I don’t have experience with any other language that uses extensions in such a significant way.

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

A quick reading.


Nicola

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

This is a good example of the arguments against the proposal. Let me try to
summarize and address them:

1) using a convention is sufficient to declare intent
2) the programmer changing the file should know what he is doing, so a
comment should be enough
3) since the programmer has access to the code, he can just change the
access level, so there is no need to go beyond the file scope
4) given the previous arguments, this small addition is not worth
increasing the language complexity

Now, let's pretend that "let" is not in the language and run it through the
list to see if it's worth adding:

1) there is a common convention of using ALL_CAPS for constants (used in
C). Another common convention is to use "k" in front of the name. Cocoa
APIs use it in many places. "let" not need. Check.
2) if the programmer is expected to have the proper knowledge about the
file he is in, this is a must for variables / constants in the *local*
scope. "let" not needed. Check.
3) since the programmer has access to the code, he can always just change
let to var. The compiler cannot help with that. "Let" doesn't protect then,
so it's not needed. Check.
4) This definitely increases the language and implementation complexity,
and especially given (3), it would be an addition that can be easily worked
around, so why bother? Check.

And yet, it is in the language because it backs a very important concept
(immutability) and lets the compiler enforce it. Moreover, Xcode keeps
track of this, and if you declare something as var and use it as a
constant, it will actively suggest that you change it to "let".

We should have "scoped" (or "local") for exactly the same reasons. Most
likely, any argument against it would apply to "let" in much the same way
(and the arguments for it are also similar to arguments for having "let").
And it would be great if Xcode noticed that you use something only
internally and actively suggested to make it scoped. The important concepts
that "scoped" would back are encapsulation and information hiding. Just as
immutability, encapsulation is a fundamental (and related!) concept, so I
don't think that the proposal needs to sell itself through compelling
examples. We all know what it's about.

···

On Fri, Feb 26, 2016 at 8:39 PM Stephen Celis via swift-evolution < swift-evolution@swift.org> wrote:

> What is your evaluation of the proposal?

I don't think it makes its case well. The alternatives it lists are
sufficient and, if anything, mentioning them makes their case equally well
(or better).

The suggestion that the current behavior "forces a one class per file
structure" is misleading. While it may require a careful API designer to
use one file to scope truly private behavior, they are free to implement
other parts of this class/structure/enumeration (and others) elsewhere.

The one limitation of splitting logic over many files (a limitation the
proposal does not mention) is that stored properties can only be defined in
the initial definition of a class/structure. I can see a separate case (and
proposal) to be made for allowing stored properties to be defined in
extensions as long as the class/structure definition exists in the same
module. I also remember reading a lukewarm discussion here around removing
this limitation.

> Is the problem being addressed significant enough to warrant a change to
Swift?

No. The problems it attempts to address can already be solved with the
existing levels of access control. It also feels incongruous alongside them
and would make Swift's design more confusing and complex.

I believe that for those that want it, this kind of scoping could be
enforced using a linter.

> Does this proposal fit well with the feel and direction of Swift?

No. Swift has lately been removing, not adding, complexity. The bar for
new language features is being set rather high, and this proposal in its
current state is not convincing enough.

> If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

I don't know of any other languages that have 4 levels of access control.
I have used languages that have more traditional "protected" and "private"
inheritance-based access control (the latter of which is similar to that
suggested in this proposal). I believe the Swift team carefully considered
prior art and thoughtfully designed the current setup (as described in
https://github.com/apple/swift/blob/master/docs/AccessControl.rst\).

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

I read through the proposal and have thought extensively about the
existing design.

--
Stephen

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Code organization of having most methods in extensions would not work for
extensions that need access to internal state marked with "scoped". For
this specific use case, one class per file + "private" works perfectly.
This code organization makes sense for very large classes but not for
smaller and related classes.

That said, I think that even in this case, "scoped" would help with hiding
helper methods that are necessary to implement protocols but are useless in
other places in the same file.

I do think that the name "private" is an unfortunate choice because it
means something else in other mainstream languages, but the access level
that it provides is very useful and a much better alternative than "friend"
in C++. However, we still need "scoped" (private in C++) to declare and
enforce encapsulation at the scope (class or extension) level.

···

On Sat, Feb 27, 2016 at 10:27 AM plx via swift-evolution < swift-evolution@swift.org> wrote:

   - 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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

If the primary goal is to keep the language small, this would be a good
argument for removing "private". But if the goal is clarity and
correctness, then "scoped" will help with both. There are many constructs
in the language already that are not strictly necessary and can be
expressed via other means. For example, anything that you can do with
"guard", you can do with "if". But they are there because they help clarity
and correctness. "scoped" is like that.

···

On Sat, Feb 27, 2016 at 3:00 PM Taras Zakharko via swift-evolution < swift-evolution@swift.org> wrote:

   - What is your evaluation of the proposal?

I am agains it. My rationale below.

One of Ilya’s main points is that encapsulation is a core principle of OOP
and that Swift lacks tools to implement this properly. I think the
terminology is somehow unlucky here. Encapsulation more universally refers
to bundling of data and code that operates of that data, and this is indeed
one of the things that defines OOP as a programming style. However, it
seems that Ilya refers to data hiding, which often accompanies
encapsulation. I believe it is important to distinguish these two concepts.
Encapsulation as I use here is a particular problem-solving style while
data hiding (what Ilya stresses) is a technique for enforcing program
correctness. It is also not true that data hiding is a fundamental
component of OOP languages, some of the popular ones don’t have any data
hiding (e.g. Python).

Data hiding obviously has an important core function, which is, as I
mentioned above — making sure that the program is correct, by only exposing
the interface other components should have safe access to. Swift has data
hiding, only the philosophy of that data hiding is a bit different from the
mainstream implementations. The scope of the most restricted access is the
file. If I understand the background of this design decision correctly,
goal here is to allow the programmer to be as flexible as possible about
what can access what but also to ‘force' them to think about their code
design and code layout (by organising interrelated stuff by files). Is this
an ideal decision to the problem? Certainly not — it imposes some
inconveniences on the programmer and can also compromise code safety (as
Illya points out). Is this a good, elegant decision? In my opinion, yes.

I can also understand Illya’s point that a local scope access might
improve code safety in certain cases. What I disagree with is that these
cases are prominent or problematic enough to deserve such attention. Adding
new access modifiers makes the rules of the language more complicated, but
I seriously doubt that a ‘local’ specifier will make programmers
significantly more productive or prevent any number of serious bugs. So
far, arguments presented were more of the ideological nature and while they
are formally correct, I do not think that the actual end effect is as
dramatic as described. In addition, I would like to point out that ‘local’
is also only an approximation — it does not solve the safety problem
entirely. It merely functions as an additional safeguard agains programmer
error. The actual problem can be only solved by specifying full rules what
is accessible from where and under which conditions. A system like that
probably won’t be practical enough for everyday programming at this point.

I also think that the comparison with ‘let/var’ as brough try Ilya is
flawed — variable mutability concerns the basic design of the data
structure/algorithm itself, while data hiding concerns the fragility of a
data structure. I understand that the boundary between these two is very
fluent, as one can portray fragility as design by encapsulating/hiding
parts of the implementation. However, we are talking about actual
implementations (e.g. variables, code etc. that the implementation use)
rather then the abstract structure of implementations.

To sum it up: Swift already supports an elegant data hiding mechanism
which IMO solves the biggest issue: components are already shielded agains
incorrect EXTERNAL use. It remains prone to incorrect use in the same file.
As far as I am concerned, the practical consequences of this are
negligible. I have some experience with languages that do not have any data
hiding whatsoever and I can’t say that I ever had any issues with messing
up the private and public interfaces — neither in my own code nor in code
written in collaboration with other people. In this regards do not believe
that ‘local’ will add anything of value to the language, however it will
make the rules more complicated.

   - Is the problem being addressed significant enough to warrant a
   change to Swift?

   - Does this proposal fit well with the feel and direction of Swift?

I do not know. The problem of code safety is a fundamental one, but I am
not sure that Swift has ever had the ambition to solve all of them. For me,
the offered solution is good enough.

   - If you have used other languages or libraries with a similar
   feature, how do you feel that this proposal compares to those?
   - How much effort did you put into your review? A glance, a quick
   reading, or an in-depth study?

I have read the proposal, and participated in the original discussion. I
can’t claim to have performed an in-depth study though.

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Doug Gregor

Review Manager
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I admit I was surprised by the semantics of private in Swift when I first

discovered it,

And so will be most of newcomers.

But I’ve not been overly bothered by it, and I’ve always kept a fairly

strict one class per file > anyway.

It is still useful to have "scoped" in one class per file structure. If the
code is organized in a way where internal state is hidden in one scope and
other functions that don't require access to the internal state are added
via extensions, "scoped" can hide the internal state from extensions and
also hide implementation details of each extension from other extensions in
the same file.

I also don’t like that the proposal adds another keyword/layer of scoping.

Today, there is no other way to express what "scoped" does except
by putting each scope into a separate file. This is very restricting and
makes it more difficult to put related code in the same file (you lose any
the help that the compiler could give you with protecting against using
private APIs).

If people really wanted Scoped Access Level, I would have suggested

modifying the meaning > of private to correspond to Scoped Access Level and
not add another keyword like this > proposal.

"private" as it is today serves an important purpose of allowing access to
internal state of related classes. It solves the same problem as "friend"
in C++. "scoped" is necessary to hide implementation details completely,
regardless of where the code is.

I don’t find it worthwhile enough to warrant adding another scoping level

and confusing > newcomers.

You acknowledge that the meaning of "private" is already confusing to
newcomers from other languages. Having "scoped" would only reduce the
confusion by providing what newcomers are likely already used to. If we
could rename "private" to something else that clearly means "file level
access", there would be no confusion at all. Newcomers would just map
"private" that they are used to in other languages to "scoped" in Swift.

Yes, all the other languages I have used have had Scoped Access Level,

but I don’t mind > Swift’s current file access level.

Neither do I. I think it's great to have a file based access level. It
might be better than "friend" in C++. "scoped" provides the functionality
similar to "private" in C++ and is valuable because it would provide
clarity of intent that can be enforced by the compiler and help ensure code
correctness.

···

On Sun, Feb 28, 2016 at 2:08 PM David Hart via swift-evolution < swift-evolution@swift.org> wrote:

*What is your evaluation of the proposal?*

-1

I admit I was surprised by the semantics of private in Swift when I first
discovered it, because I was used to private in other languages
corresponding to Scoped Access Level. But I’ve not been overly bothered by
it, and I’ve always kept a fairly strict one class per file anyway. I also
don’t like that the proposal adds another keyword/layer of scoping. If
people really wanted Scoped Access Level, I would have suggested modifying
the meaning of private to correspond to Scoped Access Level and not add
another keyword like this proposal.

*Is the problem being addressed significant enough to warrant a change to
Swift?*

I’d say no.

*Does this proposal fit well with the feel and direction of Swift?*

I don’t find it worthwhile enough to warrant adding another scoping level
and confusing newcomers.

*If you have used other languages or libraries with a similar feature, how
do you feel that this proposal compares to those?*

Yes, all the other languages I have used have had Scoped Access Level, but
I don’t mind Swift’s current file access level.

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

A thorough read.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

+1

*Fortifies extension-based approach*
The change fits well with Swift's style of splitting types' subsystems and
protocol conformances into their own extensions. This modularity is an
important tool in managing complexity and assuring quality/safety (key
Swift values), and the language should encourage this pattern. It'll become
only more relevant if extensions within the same module will in the future
be allowed to add stored properties, a change which would fit very well
with this one.

*Why not file split & use private*
Splitting each extension into a separate file is impractical because if
properly done, the size of the individual extensions is usually quite small.

*Self-documents extension API*
It makes API per extension more self-documenting – it immediately tells you
what the extension exposes to the rest of the file and what is an
implementation detail.

···

On Mon, Feb 29, 2016 at 9:45 PM, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

>
https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md

> • What is your evaluation of the proposal?

I don't think it's a good idea.

I think the source of our disagreement is expressed in this paragraph from
the proposal:

> It forces a one class per file structure, which is very limiting.
Putting related APIs and/or related implementations in the same file helps
ensure consistency and reduces the time to find a particular API or
implementation. This does not mean that the classes in the same file need
to share otherwise hidden APIs, but there is no way to express it with the
current access levels.

This reflects a view that grouping APIs into files is a purely stylistic
choice. You can move APIs around freely and organize them however you like.
If Swift gets in the way of your preferred arrangement, Swift should be
changed to allow it. The most important thing is that you be free to
express yourself artistically through the medium of the file system.

I believe that view is mistaken. Swift has a very opinionated view of how
code should be organized into files; they are as semantically meaningful as
declaration blocks.

The purpose of a file is to implement one concern—essentially, one logical
piece of the module's functionality with its own self-contained
implementation details. A concern is not necessarily fully represented by a
single type; a type may include several concerns, and several types may
implement one concern. (It's not a coincidence that a file can contain
several types and a type can be split across several files.) The precise
boundaries of a concern are a little nebulous, especially when you build
convenience APIs which don't depend on anything private, but it's usually
roughly clear what they are.

When you want to use `local`, that usually means you're not organizing
your code the way Swift thinks you should. It's no wonder Swift seems to
not be expressive enough: You're fighting the language.

Now, there are two exceptions to this general rule, but I think they're
both best handled in other ways.

The first: Sometimes Swift will not allow you to move certain things to
separate files; for instance, only one file can declare stored properties
on a type, and so stored properties must either be made more visible than
they should be, or several concerns must be mixed into a single file. I
think this is best handled by allowing extensions to declare stored
properties and other such one-file-only constructs, rather than by
complicating access control.

The second: Sometimes a particular concern has an especially complicated,
self-contained "sub-concern" which has implementation details of its own.
You would like to keep the sub-concerns implementation details private from
the containing concern, but the sub-concern is *itself* an implementation
detail of the containing concern, so you *also* want to keep the
sub-concern private from other, unrelated concerns. In these cases, some
sort of more nuanced access control would be better—but even then, I don't
think `local` is actually a very good way to do it.

There's nothing about a declaration block that makes it a natural choice
for scoping declarations. `local` hides the declaration from containing and
sibling declaration blocks and exposes it to nested declaration blocks. But
if concerns often transcend type boundaries, surely sub-concerns do as
well, so `local` will often be either too limiting or not limiting enough.

To properly handle this problem, we would be better off coming up with
some way to limit the scope of `internal` to only particular files which
need to interface with that file's concern. `internal` would expose the API
to your file's "clients"—by default all files in the module, but
potentially narrowed down to a particular subset—while `private` would
remain as something truly limited to a single file.

However, that approach is rather complicated, bordering on the horror of
C++ `friend` declarations. Ultimately, I just don't think it's too large of
a burden to say, "You have a three-level namespace, and anything that
crosses files goes into `internal`; if something in `internal` is only
meant to be used in a particular file, show some discipline."

> • Is the problem being addressed significant enough to warrant a
change to Swift?

As I often say, the problem is arguably significant enough, but I don't
think the solution is the right one.

> • Does this proposal fit well with the feel and direction of Swift?

I don't think so. Swift has strong, opinionated ideas about how code
should be organized; this proposal doesn't follow that pattern.

> • If you have used other languages or libraries with a similar
feature, how do you feel that this proposal compares to those?

I've used languages with file-based scoping, and languages with arbitrary
lexical scoping, but not languages with both.

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

In addition to reading the present proposal, I also participated fairly
extensively in discussions about it and read previously-posted reviews.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I believe that view is mistaken. Swift has a very opinionated view of how

code should be organized into files;

If that is true, Swift should enforce it. Right now it doesn't, so this is
open to interpretation.

they are as semantically meaningful as declaration blocks.

Agreed. Which is why some people may want to group related code into the
same file but still want to hide implementation details at the scope level.

The purpose of a file is to implement one concern—essentially, one

logical piece of the module's functionality with its own self-contained
implementation details. A concern is not necessarily fully represented by a
single type; a type may include several concerns, and several types may
implement one concern. (It's not a coincidence that a file can contain
several types and a type can be split across several files.) The precise
boundaries of a concern are a little nebulous, especially when you build
convenience APIs which don't depend on anything private, but it's usually
roughly clear what they are.

"concern" is a very vague term. Implementation details of a type and hiding
of its internal state and internal helper functions is much more concrete.
The model that you describe is very good about sharing implementation
details of several classes and hiding them as a whole. It's a good
alternative to C++ "friend". However, this model doesn't work well for
hiding implementation details and protecting invariants of a single type.
Most types and their extensions are small, and creating files for every
single one of them only to hide implementation details is very impractical.
For example, most people just put all extensions of one type in one file
for convenience. Carefully separating every scope and protecting its
implementation details in a separate file is very tedious. It's like
putting every paragraph of a chapter in a separate file. I haven't seen
anyone do it. Instead, people just forgo access control altogether or, at
best, provide much more access than they really mean to.

When you want to use `local`, that usually means you're not organizing

your code the way Swift thinks you should. It's no wonder Swift seems to
not be expressive enough: You're fighting the language.

Or maybe the language is fighting me :–) If this was C, and the issue was
to protect internal global variables, this reasoning would be true. But
with types, most of the time, invariants and internal state exist at the
type level, and right now there is no direct way of protecting them. Using
files for this purpose is very indirect, and the meaning is open to
interpretation. For example, another review compares access levels with
exports. Semantically they are not the same, even if the end result is
hidden APIs. Scoped level access is very clear, direct, and natural for
anyone new to the language and expresses a very important concept.

The first: Sometimes Swift will not allow you to move certain things to

separate files; for instance, only one file can declare stored properties
on a type, and so stored properties must either be made more visible than
they should be, or several concerns must be mixed into a single file. I
think this is best handled by allowing extensions to declare stored
properties and other such one-file-only constructs, rather than by
complicating access control.

I think that stored properties in extensions would be a great addition to
the language, but it has nothing to do with access level. If / when Swift
gains this feature, hiding these stored properties in the scope of the
extension would be just as important as it is now for stored properties.
Exactly the same logic applies to extensions.

The second: Sometimes a particular concern has an especially complicated,

self-contained "sub-concern" which has implementation details of its own.
You would like to keep the sub-concerns implementation details private from
the containing concern, but the sub-concern is *itself* an implementation
detail of the containing concern, so you *also* want to keep the
sub-concern private from other, unrelated concerns. In these cases, some
sort of more nuanced access control would be better—but even then, I don't
think `local` is actually a very good way to do it.

I am surprised by this -- it's exactly the kind of problem that "scoped"
solves, and very directly. Why wouldn't it be a good way to do it?

There's nothing about a declaration block that makes it a natural choice

for scoping declarations. `local` hides the declaration from containing and
sibling declaration blocks and exposes it to nested declaration blocks. But
if concerns often transcend type boundaries, surely sub-concerns do as
well, so `local` will often be either too limiting or not limiting enough.

"concern" is a very vague term, but I think that most of the time,
"concern" spans one type -- it protects internal state and internal APIs
that may manipulate that state in an unsafe way or make some assumptions
that are generally not true. "scoped" provides a very clear expression of
intent for this very common case. Most of the time, "friend" in C++ is not
needed. Similarly, most of the time, "private" could be replaced by
"scoped". Internal APIs that span several types are exceptions, not the
rule.

      • Does this proposal fit well with the feel and direction of Swift?

I don't think so. Swift has strong, opinionated ideas about how code

should be organized; this proposal doesn't follow that pattern.

It actually does:
public -- visible outside of module
internal -- visible in all files
private -- visible in one file
scoped -- visible in part of a file
for the sake of the argument, we could add
micro -- visible on one line

···

On Mon, Feb 29, 2016 at 5:45 AM Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

>
https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md

> • What is your evaluation of the proposal?

I don't think it's a good idea.

I think the source of our disagreement is expressed in this paragraph from
the proposal:

> It forces a one class per file structure, which is very limiting.
Putting related APIs and/or related implementations in the same file helps
ensure consistency and reduces the time to find a particular API or
implementation. This does not mean that the classes in the same file need
to share otherwise hidden APIs, but there is no way to express it with the
current access levels.

This reflects a view that grouping APIs into files is a purely stylistic
choice. You can move APIs around freely and organize them however you like.
If Swift gets in the way of your preferred arrangement, Swift should be
changed to allow it. The most important thing is that you be free to
express yourself artistically through the medium of the file system.

I believe that view is mistaken. Swift has a very opinionated view of how
code should be organized into files; they are as semantically meaningful as
declaration blocks.

The purpose of a file is to implement one concern—essentially, one logical
piece of the module's functionality with its own self-contained
implementation details. A concern is not necessarily fully represented by a
single type; a type may include several concerns, and several types may
implement one concern. (It's not a coincidence that a file can contain
several types and a type can be split across several files.) The precise
boundaries of a concern are a little nebulous, especially when you build
convenience APIs which don't depend on anything private, but it's usually
roughly clear what they are.

When you want to use `local`, that usually means you're not organizing
your code the way Swift thinks you should. It's no wonder Swift seems to
not be expressive enough: You're fighting the language.

Now, there are two exceptions to this general rule, but I think they're
both best handled in other ways.

The first: Sometimes Swift will not allow you to move certain things to
separate files; for instance, only one file can declare stored properties
on a type, and so stored properties must either be made more visible than
they should be, or several concerns must be mixed into a single file. I
think this is best handled by allowing extensions to declare stored
properties and other such one-file-only constructs, rather than by
complicating access control.

The second: Sometimes a particular concern has an especially complicated,
self-contained "sub-concern" which has implementation details of its own.
You would like to keep the sub-concerns implementation details private from
the containing concern, but the sub-concern is *itself* an implementation
detail of the containing concern, so you *also* want to keep the
sub-concern private from other, unrelated concerns. In these cases, some
sort of more nuanced access control would be better—but even then, I don't
think `local` is actually a very good way to do it.

There's nothing about a declaration block that makes it a natural choice
for scoping declarations. `local` hides the declaration from containing and
sibling declaration blocks and exposes it to nested declaration blocks. But
if concerns often transcend type boundaries, surely sub-concerns do as
well, so `local` will often be either too limiting or not limiting enough.

To properly handle this problem, we would be better off coming up with
some way to limit the scope of `internal` to only particular files which
need to interface with that file's concern. `internal` would expose the API
to your file's "clients"—by default all files in the module, but
potentially narrowed down to a particular subset—while `private` would
remain as something truly limited to a single file.

However, that approach is rather complicated, bordering on the horror of
C++ `friend` declarations. Ultimately, I just don't think it's too large of
a burden to say, "You have a three-level namespace, and anything that
crosses files goes into `internal`; if something in `internal` is only
meant to be used in a particular file, show some discipline."

> • Is the problem being addressed significant enough to warrant a
change to Swift?

As I often say, the problem is arguably significant enough, but I don't
think the solution is the right one.

> • Does this proposal fit well with the feel and direction of Swift?

I don't think so. Swift has strong, opinionated ideas about how code
should be organized; this proposal doesn't follow that pattern.

> • If you have used other languages or libraries with a similar
feature, how do you feel that this proposal compares to those?

I've used languages with file-based scoping, and languages with arbitrary
lexical scoping, but not languages with both.

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

In addition to reading the present proposal, I also participated fairly
extensively in discussions about it and read previously-posted reviews.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Swift's access modifier control is about code unit access levels. This

proposal breaks this conceptual model. Now instead of thinking of publicly
exposed APIs, APIs using only within the context of the module, and APIs
local to the file, I now need to parse semantic scopes to understand where
a piece of code can be used.

Basically Swift's model is:

  - public exports
  - internal exports
  - no exports

This proposal doesn't fit anywhere in that model.

This is one interoperation. Here is another that makes it fit perfectly
well:

- public: exports symbol to all files
- internal: exports symbol to module files
- private: exports symbol to one file containing the scope
- scoped: no export

Yes, this basically what private is other languages. However,

traditionally "private scope" implementations have severe weaknesses in the
ability to expose the implementation details only to helper code within the
same file. Creating local re-invents part of that.

Yes, and "private" is great for that. We can have both. They serve
different purposes.

···

On Mon, Feb 29, 2016 at 12:31 PM David Owens II via swift-evolution < swift-evolution@swift.org> wrote:

*What is your evaluation of the proposal?*

-1. I disagree with the starting motivation points, so it's hard to really
see the value in the rest of the arguments.

*Is the problem being addressed significant enough to warrant a change to
Swift?*

No.

*Does this proposal fit well with the feel and direction of Swift?*

No. Swift's access modifier control is about code unit access levels. This
proposal breaks this conceptual model. Now instead of thinking of publicly
exposed APIs, APIs using only within the context of the module, and APIs
local to the file, I now need to parse semantic scopes to understand where
a piece of code can be used.

Basically Swift's model is:
  - public exports
  - internal exports
  - no exports

This proposal doesn't fit anywhere in that model.

*If you have used other languages or libraries with a similar feature, how
do you feel that this proposal compares to those?*

Yes, this basically what private is other languages. However,
traditionally "private scope" implementations have severe weaknesses in the
ability to expose the implementation details only to helper code within the
same file. Creating local re-invents part of that.

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

I've read the review and followed much of the conversation, including
participating in the previous thread on this topic.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Does this proposal fit well with the feel and direction of Swift?

Yes and no. Yes, because it a

Sorry, I forgot to finish the sentence. :see_no_evil:

Does this proposal fit well with the feel and direction of Swift?

Yes and no. Yes, because it could in some small percent increase the safety of our programs. No, because it would strip away the "composability" of our code.

Pozdrawiam – Regards,
Adrian Kashivskyy

···

Wiadomość napisana przez Adrian Kashivskyy via swift-evolution <swift-evolution@swift.org> w dniu 04.03.2016, o godz. 10:09:

What is your evaluation of the proposal?

Neutral. While I agree that scoped access levels can be useful in some cases, I don't see much long-term profits, especially when using a "protocol conformance in an extension" style:

struct Foo {
  private func doSomethingInternal()
}

extension Foo: Barable {
  func bar() {
    doSomethingInternal()
  }
}

The above would not be possible to achieve with scoped access levels.

Is the problem being addressed significant enough to warrant a change to Swift?

I don't think so. File access level does the job well for me.

Does this proposal fit well with the feel and direction of Swift?

Yes and no. Yes, because it a

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Yes, Ruby, PHP, C++. In those languages, there are no file access levels and so the scoped access levels are the only ones which allow to hide the implementation details from the outside world. In Swift, however, file access level allows us to do the same with additional bonus – we can use those `private` members in extensions.

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

Pozdrawiam – Regards,
Adrian Kashivskyy

Wiadomość napisana przez Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> w dniu 26.02.2016, o godz. 20:05:

Hello Swift community,

The review of SE-0025 “Scoped Access Level" begins now and runs through March 3, 2016. The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md
Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

https://lists.swift.org/mailman/listinfo/swift-evolution
or, if you would like to keep your feedback private, directly to the review manager. When replying, please try to keep the proposal link at the top of the message:

Proposal link:

https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md
Reply text

Other replies
<GitHub - apple/swift-evolution: This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

What is your evaluation of the proposal?
Is the problem being addressed significant enough to warrant a change to Swift?
Does this proposal fit well with the feel and direction of Swift?
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/master/process.md
Thank you,

-Doug Gregor

Review Manager

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Currently there is no (good) way to limit extension methods to use only within the containing extension. I think the added clarity of a compiler-enforced access to “local” extension methods will be a solid win.

I'm seeing a lot of people say "I want to keep things private to a particular extension within a file". I'm still not convinced if that's a worthy goal, but if it's what people want, I'd like to suggest an alternate semantic: `scoped` limits visibility to a particular top-level declaration block and all of the blocks nested inside it. That is, `local` declarations in a nested block are visible to the blocks it's inside, up to but not including the file scope.

  class Foo {
    struct Bar {
      var parentFoo: Foo
    }
    var myBar: Bar
    
    // Here, neither of the implementationDetails is visible.
  }
  
  extension Foo {
    func doSomething() {
      myBar.implementationDetail1()
    }
    
    extension Bar {
      scoped func implementationDetail1() {
        parentFoo.implementationDetail2()
      }
    }
    
    scoped func implementationDetail2() {
      print("All this compiles!")
    }
  }
  
  extension Foo {
    // Here, neither of the implementationDetails is visible.
  }

···

--
Brent Royal-Gordon
Architechies

My understanding is that this comes about from a simple idea - someone wants to have objects which have some protection of their API from the outside world, and to limit the interface between those objects from exposing internal implementation details in order to maintain invariance.

No, this comes from the desire to document the specific members that are accessed by other scopes in the file.

I think it makes a significant difference when reading code.

A.

The key differences are:

1) You are already the file implementing on that area of code if you are in the same file.
2) The file shouldn't be that big whereas the module is likely to have hundreds of files and several people on it.
3) There are performance benefits to be had with private (at least when Whole Module Optimisation is off).

I'm not too worried if it does get included it just seems the gains will be quite small. Can you point me to some code that would really benefit or where the lack of it has caused mistakes or problems in the past?

Joseph

···

On Feb 27, 2016, at 1:24 AM, Ilya Belenkiy <ilya.belenkiy@gmail.com> wrote:

By the same logic, the comment alone is enough, the private keyword isn't necessary. The benefit of scoped is that the intent would be enforceable by the compiler.

On Fri, Feb 26, 2016 at 6:48 PM Joseph Lord via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 26, 2016, at 7:05 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

The review of SE-0025 “Scoped Access Level" begins now and runs through March 3, 2016. The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md

-0.1
There is nothing wrong with this proposal but I just don't see the value over private (plus a little doc comment if absolutely necessary).

Downside is more language to learn / encounter and complexity to maintain.

I've read the proposal but not the preceding discussion.

Joseph
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Thanks Drew! This example is much more compelling than the one in the proposal, and I hope there are more that come to light in this thread. I do, however, think that property behaviors would be a better solution to that particular problem:

···

On Feb 27, 2016, at 12:46 AM, Drew Crawford via swift-evolution <swift-evolution@swift.org> wrote:

What i mean here is simply [...]

--
Stephen

let/var does actually enforce intent. I have to actually change let to var before I can modify a value.

This proposal does not enforce intent. I do not have to change from “scoped”/“local” to “private” in order to break encapsulation - I just add another method to the class.

That is my primary complaint against scoped/local access - it is an arbitrary limitation that does not improve my ability to express what is invariant in a class beyond private. I still have to assume any change within a file may have abused scoped access permissions until I evaluate said change.

Some of my personal opinion on this may be the volume of changes I try to evaluate as line-by-line diffs. A change from let to var or from non-optional to IUO shows up as part of such a change. Where code was lexically inserted within a file does not. Unlike var/let and optionals, I don’t believe scoped allows a reviewer to know that invariants were abused without an analysis of the file which was modified.

More so, I can request and expect a certain evaluation about a developer changing a variable from being constant to non-constant, or from non-optional to optional. “Scoped”/“Local” does not require the developer to change a line of code to get different behavior - they simply change the location where their code is inserted. This is because access control only affects behavior at the call site, not the actual functionality of a variable or method.

If a developer thinks their method requires access to scoped/local properties or methods, they will just add that to the body of the class. The *only* thing that makes such a change safe is understanding of the invariants of the class - and scoped/local do not affect the behavior of these invariants or a developer’s ability/need to understand them whatsoever.

My understanding of your argument is that let/var are justified because they improve safety and more strongly indicate intent, and that scoped/local are justified for the same reason.

My argument, however, is that scoped/local do not meaningfully improve safety or indicate intent over a mix of private and partitioning code into files. Scoped/local allows the access control model to scale to having more code be included in a single file.

-DW

···

On Feb 27, 2016, at 8:57 AM, Ilya Belenkiy via swift-evolution <swift-evolution@swift.org> wrote:

This is a good example of the arguments against the proposal. Let me try to summarize and address them:

1) using a convention is sufficient to declare intent
2) the programmer changing the file should know what he is doing, so a comment should be enough
3) since the programmer has access to the code, he can just change the access level, so there is no need to go beyond the file scope
4) given the previous arguments, this small addition is not worth increasing the language complexity

Now, let's pretend that "let" is not in the language and run it through the list to see if it's worth adding:

1) there is a common convention of using ALL_CAPS for constants (used in C). Another common convention is to use "k" in front of the name. Cocoa APIs use it in many places. "let" not need. Check.

2) if the programmer is expected to have the proper knowledge about the file he is in, this is a must for variables / constants in the *local* scope. "let" not needed. Check.
3) since the programmer has access to the code, he can always just change let to var. The compiler cannot help with that. "Let" doesn't protect then, so it's not needed. Check.
4) This definitely increases the language and implementation complexity, and especially given (3), it would be an addition that can be easily worked around, so why bother? Check.

Thanks Drew,

This and your previous email have provided a good use case and shifted my view towards the positive. +0.5 now as I'm still not sure it a definite win worthy of the addition to the language but I think it might be. I'll continue thinking about it.

Joseph

···

On 27 Feb 2016, at 05:46, Drew Crawford via swift-evolution <swift-evolution@swift.org> wrote:

I’m unsure what you are imagining here - compiler protection of class details from within a closure completion handler that is also part of your class definition?

What i mean here is simply

class Foo {
  ///it is undefined behavior to access this variable except from specialQueue
       private var _specialQueueOnly = 0

  public var specialQueueOnly: Int {
    var i: I! = nil
    dispatch_sync(specialQueue) {
      i = _specialQueueOnly
    }
    return i
  }
}

The lynchpin of this defensive programming technique is that only these 12 lines of code have any risk of a threading bug, and we can trivially inspect the 12 lines. So our safety hangs on two tentpegs:

1. That "private" in "private var _specialQueue" is compiler enforcement against other files trying to access this ivar
2. That the file itself is 12 (or other minimal number of) lines and trivially inspectable.

Should we violate any of these constraints, we lose our safety.

It may be that I’m unfamiliar with UITableViewCellContentView (as it is not documented).

Well, that is kind of the point: Objective-C's access control worked, and prevented you from knowing about this class.

But to provide a more accessible illustration, consider the case where we have some motivation to hide Foo from the rest of our framework[/module/target/application/executable/library]. This is more likely to happen in a UIKit-sized project, where there are hundreds of public classes, and probably thousands of "internal" ones, and a typical class has motivation to touch 5 or 6 other classes, of the thousands that may be available.

In Swift, each internal class is visible to every other class. But that is not especially workable at UIKit scale; if every class can potentially reach every other class we are in for an adventure when one of the hundred developers on your team decides that some UITableView implementation detail you've never heard of should be accessing some UILocalNotification detail you've also never heard of. So we need some kind of "fencing" within a large framework to make good neighbors.

This is solved very easily: we can group several related classes into one file, and some of the classes are private. Many ordinary people today group related classes into a file as a fencing mechanism even not at UIKit-scale. So a file can access all of its own classes, but not all the classes of other files. That creates the "fence", and now your coworker cannot draw a line between some UITableView secret class and some UILocalNotification secret class, and your desk will not be dented from the impact of your forehead.

The problem now is that while fixing this situation we have broken one of our safety tentpegs. We earlier required that Foo.swift be only 12 lines for thread safety, but now Foo is contained in a larger file so as to create a fence. So we can solve one of these problems or the other one, but never both at the same time.

"local" effectively resolves this dilemma, because if our _specialQueueOnly variable is local, then it is not the /file/ which must be kept to 12 lines, but the /scope/. So we could group an unlimited number of classes in Foo.swift, with no loss of confidence for our thread safety.

A better approach might be to realize that if global scope, target scope, and file scope do not solve the visibility problem, perhaps yet another scope will not totally solve the problem either. I fully expect Apple will need a "vendor" scope for example (so that UIKit and CoreAnimation, two public frameworks, can still have private APIs between them), and I bet there are many more kinds of scopes that have not yet occurred to me.

Behind that realization lies the Rust system, which divorces visibility from these arbitrary scopes that we seem to be struggling to fit into. But that proposal isn't before us, and this one is. I prefer going somewhere to staying here.

Drew

On Feb 26, 2016, at 10:34 PM, David Waite <david@alkaline-solutions.com> wrote:

On Feb 26, 2016, at 8:44 PM, Drew Crawford via swift-evolution <swift-evolution@swift.org> wrote:
Threading is one especially pernicious case. If I have an ivar that is only safe for access from one thread, I *need* compiler enforcement. I *need* a guarantee that this ivar is only accessed through public interface methods that can be audited to be threadsafe. Simply a doccomment that says "bad programmer, don't do it" is not enough.

I’m unsure what you are imagining here - compiler protection of class details from within a closure completion handler that is also part of your class definition? I believe you would only get this with this local scope proposal if you structured your code such that callback blocks were functions outside your type definition.

If you are talking about access or modification of the inner state of a class and a manual audit of safety, that audit is of the file and not of the type or extension. I’m unsure if your concern is of having to split code into multiple files for safety, or that there is not a way to split code into multiple files to achieve safety in some particular scenario.

This is not even a matter of "artistic choice" of whether or not I want to follow "one file per class". I can achieve thread safety with "private" ivars and "one file per class", but if my class is UITableViewCellContentView (which is an implementation detail that should be hidden even to most of UIKit) I am now forced to expose that implementation detail to my entire team.

This places me in the unconscionable situation of choosing between whether I have thread safety or encapsulation, between whether my coworker will accidentally create a threading bug or accidentally use a class they ought not to use and I am unable to appropriately hide.

I’m not quite sure what you mean here - exposing that your class is a subclass of UITableViewCellContentView? Or that the rest of your team needs to code in different files from your class in order to maintain encapsulation?

It may be that I’m unfamiliar with UITableViewCellContentView (as it is not documented).

<snip>

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

I've followed this from the earliest discussions. I've rethought my position somewhat in response to the growing uncertainty about dropping the NS prefix, which I think exposes some very real problems with visibility in Swift.

As that situation has developed, I no longer believe this proposal goes far enough. But it does go somewhere, and we should not stay where we are.

I don’t understand how an access control proposal pertains to changes in the Foundation public API. What is the ideal end state in your mind, and in what way is this a step toward that?

-DW

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution