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

Ilya Belenkiy ilya.belenkiy at gmail.com
Mon Feb 29 21:41:10 CST 2016


> 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 at 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160301/ef39fa35/attachment.html>


More information about the swift-evolution mailing list