[swift-evolution] access control proposal

Ilya Belenkiy ilya.belenkiy at gmail.com
Mon Dec 14 06:09:45 CST 2015


All of these examples for more access control could and should be
refactored to have simple interfaces with completely hidden implementation
details.

The discussion has become unnecessarily complicated. This proposal is about
a fundamental concept of software engineering: provide a small and simple
interface that serves a particular purpose and hide implementation details.

Implementation details need to be hidden for several reasons:
- ensure that the invariant of the public API holds true regardless of how
the public API is used. For example, a stack's element count and relative
order changes only with push() and pop().
- the implementation could be changed without affecting how the API is
used. For example, a stack backed by an array could turn into a stack
backed by a linked list.
- the API that the user has to deal with is small. If the user can see the
internals, he has to constantly separate what's public (and allowed) and
what is private (and could change).

Currently, the only 2 ways to express this concept in Swift are
1) use private and put the implementation  into a separate file
2) use _ or some other convention to distinguish public APIs from the
functions that are used for implementation

Using separate files works, but it doesn't work well for related concepts.
It often makes sense to group similar concepts / interfaces / APIs in one
file. This solution comes at the expense of not being able to do this,
which I find extremely limiting. An analogy: it makes sense to put separate
chapters of a book into different files, but a file per paragraph is
extremely inconvenient.

Using _ also helps, but it's coding by convention. It is still possible and
easy to break it and break the code. And we see it all the time with ObjC
developers trying to use private APIs to fix a bug or add functionality
that is not public. It also forces the programmer to sort through all the _
in search for the APIs that he can use. And if he first sees a similar
private API, it takes a mental strain not to use it and keep searching for
the pubic version (if there is one).

Using local solves the problem an all accounts:
- the compiler enforces that the invariant holds true (as long as the
implementation is correct)
- the auto completion shows only the methods that are available instead of
showing the APIs that the user should not use
- the code can be organized in a way that makes sense with grouping of
related code into one file without introducing oversharing of APIs.

Local is also very much in line with Swift's focus on strong type system,
elimination of accidental mistakes, and code clarity. If we have guard that
can be easily replaced by an if statement, I don't see how local is a
problem. If you say "but guard helps eliminate lots of nested if
statements", I can reply "but local helps eliminate lots of tiny files".
Any arguments about cognitive overhead, making language bigger, etc. can be
argued the same way. The biggest difference though is that local is not
only about clarity and convenience, it's also about correctness. I would
even argue that local should be the default and that any other access level
modifier should be a deliberate choice by the programmer.

--
Ilya Belenkiy

On Sun, Dec 13, 2015 at 4:58 PM Brent Royal-Gordon via swift-evolution <
swift-evolution at swift.org> wrote:

> >> I mean, look, you’re right. In your case, three access modifiers isn’t
> enough to prevent this mistake; you need four. But,
> >> given four, you could provide an example that needs five. Given five,
> you could come up with one for six. Given N access modifiers, you can write
> a case where you need N + 1.
> >
> > Nope.  What we're talking about (what I'm talking about, anyway) is a
> syntactically scoped access modifier.  There is no N+1 case.  There is no
> "even more scoped".  There is no inductive case.
> >
> > I mean, it is possible to write a troll proposal for an access modifier
> visible to the 3 lines before and after the declaration, or something.
> That much I grant.  But troll proposals aside, a scoped access modifier is
> the quark of the access modifiers; there is no sensible way to go halvsies.
>
> I actually immediately thought of several things you might want:
>
> • You have two logical “groups” of properties and methods in a type, and
> you want to only permit access within a group. But both groups have stored
> properties, so they must be defined in the scope of the main type
> definition.
> • You have types nested within your type. You want methods in your own
> type to have access, but not methods in those nested types.
> • Alternatively, you have types nested within your type. You want the
> outer type to have access to things in the nested type, but not any other
> type.
>
> I don’t think these are troll proposals. They *are* a little more limited,
> a little more esoteric, but you could say the same thing about `local` vs.
> `private`. And in some cases you can emulate some of these with the four
> access modifiers you want, but again, you could say the same thing about
> `local` vs. `private`.
>
> >> Actually, for this case, there *is* a way to achieve better safety
> without additional access modifiers. Create a new file and call it
> Synchronized.swift:
> >
> > This is the solution that I ship.  I think if we don't add a new
> visibility modifier, we should definitely do this in the standard library,
> because "resource synchronization" is a problem most modern programs have.
>
> This is not a bad idea. The only potential issue I can see with it is
> that, if it’s made too general, it might be difficult to construct a
> Synchronized instance correctly.
>
> >> My solution is to be careful when I’m writing code.
> >
> > It is actually *this* statement which is weak to an induction attack.
> We can *always* be more careful writing code.  Why do we need typechecking,
> why do we need integer overflow trapping, why do we need array out of
> bounds exceptions?  The C developer says they do not need half of Swift
> because their solution is to be more careful writing code.  The C developer
> is naive.
> >
> > But in *this* language, we adopt zero and low-cost abstractions in the
> name of safety.  A scoped access modifier is a zero-cost abstraction that
> makes the language safer.  We should adopt it.  If for no other reason,
> than consistency with the other aspects of the language design.  Either
> that or we should move integer overflow checks into the standard library
> for consistency with Synchronized, which I present as a troll proposal.
>
> But it’s not zero-cost. It’s another thing to learn, another thing to
> think about, another way you have to mentally analyze your code. It has to
> be balanced against the goal of keeping the language small and simple. And
> many people, when they do that, find this idea wanting.
>
> > I mean, I don't know about you, but I get called in to look at projects
> regularly that have a MainViewController.swift that weighs 10KLOC.  I
> didn't write it.  I didn't make the mess.  I just want tools to help me
> clean it up.  'private' is useless in that context, and it's a (sadly)
> typical context in iOS.
>
> `local` is going to be equally useless in this context, because
> MainViewController.swift will almost certainly contain one 9.999KLOC
> `class` block. Cleaning up is, practically by definition, messy and tiring.
>
> --
> 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/20151214/f205db12/attachment.html>


More information about the swift-evolution mailing list