SE-0254: Static and class subscripts

Given that even those of us in favor haven't been able to name too many use cases, I'd be interested in hearing about yours!

2 Likes

Thanks for the report—I'll take a look at that.

Here is one example: I have an API that uses key-paths to extract instances of a predefined endpoint types (bundle type with static information about bluetooth characteristics for custom peripherals we develop). These endpoints should be all static and lazy, but currently they aren't as key-paths cannot traverse static type members or static subscripts yet. One of these endpoints is computed by a subscript on some intermediate path. Even if there was no key-path limitation, this would be the next issue to overcome as all endpoints should be static. The current workaround requires me to create a single instance of those types that provide such endpoints, but that is not ideal as all of the endpoints are initialized right away with all their default values, which can be avoided with static laziness.

2 Likes

I don't find the single use case in the proposal to be very convincing. Perhaps I'm just too accustomed to how Cocoa does things, but it seems wrong to me that the metatype for Environment would be vending the environment instead of an instance. I think it should be something like Environment.common["PATH"], even if there can only be one instance (singleton).

I'm not really opposed to this change, but I'm not too thrilled either as I'm not too sure allowing metatypes to behave as collections should be encouraged.

  1. That would be extremely source-breaking.
  2. This proposal wouldn’t prevent that — you’d just have to implement it as a static generic subscript that returns a () -> Whatever, and which the trailing () at the callsite then executes.
  3. I don’t know enough about this one to comment.

I asked something similar in the pitch thread already. Here is the response:

I too am struggling to find a use case that isn't better solved some other way. The process environmental variables example from the proposal arguably should be modeled 1:1 with how the OS thinks about processes, and therefore the environmental variables would be a static var on a Process type.

  1. I shouldn't have used the word "switch". My point is that if (by design or by accident) the "angle bracket" syntax becomes a problem, then it could be deprecated in favor of square brackets, thus "not source breaking", just "a gigantic look-and-feel change".
  2. I think you misunderstood my point. I'm talking about C/C++ style tricks where malloc-like APIs can take arguments. As I point out in the original post, this goal can be solved other ways (for example, revive the "new" keyword from Swift's pre-beta past).
  3. This is deeply malloc related. It's similar to the popular "zero sized array" language extension that many compilers support. In any case, I doubt that this feature is worth using reserved/concise syntax, but the larger point is that maybe some unforeseen compiler feature might want the syntax (and as I replied to @jrose, I'm struggling to find a use case for static subscript that doesn't feel contrived or a symptom of suboptimal design).

I think we're well past the point of doing (1). We've managed to avoid having to write the angle brackets nearly as often (due to type inference and protocol extensions), and having another spelling for generic argument lists wouldn't improve things at this point.

(Personal viewpoint) In general, I think the only use of the <type> [ ... ] syntax should be for static subscripts, or not at all. Taking the syntax for (2) or (3) would be surprising, because we've set up expectations with the relationship between static/instance for properties and methods.

Doug

6 Likes

There was some thought of using it to represent dimension for Vectors, Matrices, and Fixed Arrays:

let vec3 : Int[3] = ...
let matrix : Double[2][2] = ...

of course there is also the idea of:

let vec3 : Int x 3 = ...
let matrix : Double x 2 x 2 = ...

I do find the first much easier to read, but that may just be my C background...

I suspect we'd end up with something like:

let vec3: [Int x 3] = ...
let matrix: [Double x 3 x 3] = ...

to keep the square brackets surrounding the type.

Doug

8 Likes

Very reluctant +1.

This feature should exist to keep the consistency between how instance and types can be treated, but I can't help but worry for the bad programming practices this encourages.

Pretty much anything that I can think of that would benefit from being statically-subscript-able, is something that should be mocked in a unit test, and since the static subscript operators is ... static, that can't be done.

Even the Environment example given, I find to be poorly designed. I would much rather see it implemented as:

public protocol Environment {
    public subscript(_ name: String) -> String?
}

public struct RealEnvironment: Environment { /* Bikesheddable name for example purposes */
    static let shared = Environment() // Singleton variable

    public subscript(_ name: String) -> String? { // now an instance subscript operator
        get { return getenv(name).map(String.init(cString:)) }
        set {
            guard let newValue = newValue else {
                unsetenv(name)
                return
            }
            setenv(name, newValue, 1)
        }
    }
}

Because this is an instance, it introduces a seam, which now makes it possible for a mock object to be substituted in, and used wherever an Environment is needed:

public struct MockEnvironment: Environment {
    var values: [String: String]

    init(initialValues: [String: String]) {
        self.values = initialValues
    }

    public subscript(_ name: String) -> String? {
        get { return self.values[name] }
        set { self.values[name] = newValue }
    }
}

This is just basic OO design stuff, I'm sure I'm preaching to the choir, but still. I can't think of anything that would make sense to be statically-subscript-able, that you wouldn't want to mock at some point.

PS: Now that I wrote it, I realize you could make extend Dictionary to conform to Environment as is, but that wouldn't be very clear, so I'd probably prefer having the MockEnvironment anyway, in order to better communicate intent.

2 Likes

This wouldn’t preclude that, since you could just write a “normal” static subscript function which takes a single “whatever vectors use” parameter returns the vector (or its type, depending on how things get implemented).

1 Like

Completely unnecessary, I find it hard to believe that any non-crappy code would ever actually make use of this.

But consistent with static let, var and func declarations, so very strong +1

2 Likes

What is your evaluation of the proposal?

+1!

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

Yes, it unifies the language consistency.

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

Of course.

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

Read the proposal.

  • What is your evaluation of the proposal?

Light +1. I don't think the proposal's use cases are that compelling, but it seems like something that could become more useful over time, and may enable other Swift features.

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

I don't think the lack of this feature is a significant problem, but its addition could make the language more useful without much of a downside.

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

I don't believe there's a good guide on what's expected or desired out of Swift's metatypes, so I can't answer this. In general the solution "feels" like Swift, which is as far as I can go.

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

Nope.

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

Followed the thread and read the proposal.

I get where you're coming from, but at some point, you do have to have the actual API that really accesses the environment. It'd be nice if that API wasn't getenv(_:), setenv(_:_:_:), and unsetenv(_:), and pretending that you're merely working with one instance of the actual, real environment is somewhere between silly and misleading.

1 Like

Indeed. Static subscripts would make for a nice replacement. But I think that should be an internal private API, at which point I don't really care whether it uses subscripts or methods, because it wouldn't impact end user ergonomics.

Still, I do support this just for consistency's sake, but I don't think the env example is particularly compelling, and I've struggled to find an example that is.

3 Likes
  • What is your evaluation of the proposal?

+1

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

It's not a feature that will find much use in my usual app code, but as the change is purely additive, I'm for it. It doesn't complicate the mental model and it uses the syntax (the square brackets) that is used for subscripting in other cases.

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

Yes, as I see the current feel and direction as cleaning things up and fixing the inconsistencies.

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

No comparison.

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

Read the proposal, read the thread.

The Core Team has accepted this proposal. Thank you, everyone!

2 Likes