New Access Modifier: package

While in the end I'm not in total agreement with the concept as stated, I appreciate the formulation of it and it led me to some new ideas.

I would change "encouraging correct usage" to "enforcing correct usage", and I would further add that I think the benefit of enforcing correct usage always boils down to local reasoning (e.g., an initializer that must be called just-so is made private, which enforces correct usage of the type by making the user use the robust public initializers, and the purpose of this design is that the author of the type can locally reason that the type's properties will never be configured improperly.)

Therefore, I'm arguing (at least until I'm convinced otherwise) that maybe there's only one reason for access control modifiers, which could be described as "local reasoning".

Since that's what I'm arguing, the next step would be to refute this:

to which I would point out that "a Swift package that I am currently authoring" is a waaay more "local" scope than public (and also of course it's much less "local" than "the code in this single file"), and consequently marking something packageprivate gives me tremendous local reasoning capabilities as compared to public. For example, if I have a packageprivate protocol I have the ability to physically view all of the conformances to that protocol that exist in the entire world, just by browsing the package's source files that I have locally on my computer, which is a noticeably easier task than the impossible task of viewing all conformances if the protocol is public and on GitHub (and, of course, is also a correspondingly harder task than viewing the conformances if the protocol is fileprivate.)

New Idea - General Solution

I'll finish with an idea that has just formed in my mind, which I think would qualify as a more general solution to the problem:

Continuing off the idea I offered above, imagine that we introduce a new access modifier:

private(to: RelativeScope)

where conceptually we can imagine the RelativeScope type like this:

enum RelativeScope {
    case package
    case module
    case folder
    case file
    case type
}

This current proposal would be served by:

private(to: .package) protocol AllConformancesCanBeViewed { }

These current modifiers would desugar in these ways:

internal is private(to: .module)
fileprivate is private(to: .file)
private is private(to: .type)

It would be pretty awesome to get folder to come along for the ride.

Later on we can add new overloads for this access modifier if we determine new scopes that would be useful. For example, earlier in this thread in a pseudo-code example I made use of this overload:

private(to: Any.Type)

As a final new idea, I'll mention the possibility of adding the case named extension to the imaginary RelativeScope enum, which would be useful in this way:

/* we're inside of the somewhat long DashboardView.swift */

/* ... code, code, code ... */
/* ... not visible because it's scrolled off the screen ... */

private extension DashboardView {
    var balanceReadout: some View {
        HStack {
            balanceNumbers
            currencyToggle
        }
    }

    /// I can see at a glance that this subview
    /// is not used anywhere other than in `balanceReadout`
    private(to: .extension) var balanceNumbers: some View {
        Text(viewModel.balance(in: selectedCurrency).description)
            .bold()
    }

    /// The same luxury of local reasoning applies to this one too
    private(to: .extension) var currencyToggle: some View {
        Toggle("Currency", isOn: $selectedCurrency.dollarsOrEuros)
    }
}
6 Likes