What is your evaluation of the proposal?
The proposal is well motivated and addresses an important problem but I remain unconvinced that the details of the design are the best solution for Swift. I would like to see further revision before the proposal is accepted. More on that below.
Is the problem being addressed significant enough to warrant a change to Swift?
The problem addressed is indeed significant. The primary use case presented in the proposal is well motivated and should be addressed eventually.
However, I also believe that getting the design right is far more important than introducing a solution in the Swift 5 timeframe. This is a feature that has a significant impact on the surface of the language and we now have a relatively strict source compatibiliy requirement for future changes. We need to get this feature right the first time.
Does this proposal fit well with the feel and direction of Swift?
Not in its current form.
The primary aspect of the design that I find unsatisfactory is that I strongly believe that in a language such as Swift member lookup is an operation that should not ever fail at runtime without some form of annotation at the call site.
By "disguising" a string literal as an ordinary member name (that is usually statically checked) it hides potential mistakes that would otherwise be visible at the usage site. The proposal provides sufficient motivation for introducing the sugar but unfortunately does not provide any alternative mechanism for making this potential for mistake visible to readers of code that utilizes the sugar. It asks us to sacrifice a "feature" many Swift programmers (and users of strongly typed langauges in general) enjoy: if a piece of code compiles there is no chance that you mistyped or renamed a member such that lookup will fail at runtime.
Swift does include dynamic lookup in the limited context of AnyObject dispatch. Despite having used the language full time for more than 3 years (and since the initial beta release more casually) I have not found a case where I thought using this feature would make a piece of code better. In fact, I believe the teams I have worked with would not approve a pull request that used this feature without an extremely strong and well-motivated argument for making an exception.
This is simply not a feature that the Swift programmers I know and work with think about very often and it is even mroe rarely used. It is a relatively obscure feature whose use is generally discouraged. IMO this is a reasonable argument for making AnyObject dispatch also be more visible at usage sites. The primary argument (again, IMO) against doing so is source compatibility. With this in mind, I do not believe the lack of a usage-site annotation for AnyObject dispatch is a good precedent to rely on when designing a much more general dynamic lookup feature.
One of the examples included in the proposal actually illustrates my concern very well. It includes a JSON
type that conforms to DynamicMemberLookupProtocol
with a code sample of json[0]?.name?.first?.stringValue
using using the dynamic loookup capability of the type. This example uses string literals (disguised as members) to extract data from the JSON.
In my experience, using literals rather than constants in a context like this is widely viewed as an anti-pattern, particularly when the same literal is repeated multiple times. The potential for mistake is not theoretical: people do get bitten by this problem in shipping code. Further, it provides a good example of the kind of library people will write and use in pure Swift if the proposal is accepted.
Setting aside all other concerns regarding this kind of API for working with JSON, if we rewrite the example assuming constants were defined for the dictionary keys the example becomes json[0]?[name]?[first]?.stringValue
. When we do that the sugar in the current proposal trades two braces for one dot while disguising a string literal as a member name.
Does the Swift evolution community really believe that this it is actually a good tradeoff to make? Do we want to allow libraries like this to be written and used without at least providing some hint to the reader that member names within a scope may actually be disguised string literals?
I believe a usage-site annotation appropriately provides a subtle indication that there are usually better ways to work with JSON and also informs a reader that the "member names" are actually string literals that are used to dynamically look up a value. This kind of subtle guidance and clarity is one of the things I love most about Swift.
While usage-site annotation provides important clarity in code that uses dynamic lookup the most significant benefit is in Swift code that does not use the feature at all. Readers of this code will be able to continue to rely on the fact that the compiler has verified the member names present in the code. This will not be possible (with the same degree of certainty that it is today) if this proposal is accepted as-is.
The proposal does contain a section called "Increasing Visibility of Dynamic Member Lookups". This section presents a good argument against usage-site annotation modelled after optional chaining. However, it does not mention other alternatives which have been discussed.
One alternative is an expression-level modifier mmodelled after try
. Another alternative is to introduce a scope where the dynamic lookup sugar is enabled. There may be others. The important point that should be carefully considered is that it is possible to have usage-site annotation that does not impose a significant burden on users.
In general, I don't believe the design space for usage-site annotation has been sufficiently explored. It is clear that there are options available that are significantly less burdensome than the alternative included in the proposal while still communicating clearly to a reader that a member name might be incorrect within a specific context. I strongly believe that this design space should be carefully considered before a dynamic lookup feature is introduced into Swift.
If this proposal is revised to include a usage-site annotation feature I would enthusiastically support a proposal to adopt the same annotation for AnyObject
dispatch if the potential impact on existing code was deemed acceptable.
Moving on, one other aspect of the design about which I am uncertain is addressed in the section "Make this be a attribute on a type, instead of a protocol conformance". I don't have a strong opinion here but do beleive we should very carefully evaluate the alternative before introducing this feature. We need to get it right.
In particular, I find several points in the rebuttal to the attribute alternative to be unconvincing.
Protocols describe semantics of a conforming type, and this proposal provides key behavior to the type that conforms to it.
This does not imply that attributes never describe semantics. @discardableResult
, @escaping
, @frozen
(if accepted), and many other attributes describe semantics. Protocols don't have an exclusive claim for describing semantics.
When a type uses this proposal, it provides a fundamental change to the type's behavior. While it isn't perfectly followed, attributes generally do not have this sort of effect on a type.
IMO this is actually an argument in favor of an attribute. This kind of change seems much better aligned with the kind of semantics described by @frozen
than it does with most protocols.
Attributes are syntactically very light-weight, which makes this easier to overlook - given the significant effect on a type, we prefer it to be more visible.
I find the concern with syntactic weigth at the type-declaration site and lack of concern with the absence of any indication of the presence of this feature at usage sites to be very puzzling. This is exactly backwards in my opinion. Whether an attribute or protocol is used in the type declaration is immaterial for the purposes of clarity.
If someone is looking for this information they will find it. If they are glancing at the declaration I think they are about equally as likely to notice either form of declaration. However, at the usage site where this feature will lead to mistakes (as is inevitable with dynamic lookup), people are extremely likely to miss the presence of this feature if no annotation at all is present.
Finally, I would like to briefly discuss the following:
The result type may also be any type the implementation desires, including an Optional, ImplicitlyUnwrappedOptional or some other type, which allows the implementation to reflect dynamic failures in a way the user can be expected to process (e.g., see the JSON example below).
This section of the proposal does not say anything about allowing an implementation to choose to use Swift's error system to report dynamic failures. This seems like an important omission. If the proposal does not support that it should explain why. If it does that should be documented. I appologize if this topic was addressed in earlier discussions - I don't remember whether it was or not and don't have time to read the archives.
In any case, I believe the proposal should be updated to includ this information. FWIW, my personal opinion is that a dynamic member lookup should be well integrated with Swift's error model, at least as an option for the author of a type supporting dynamic lookup.
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
I have used dynamic languages such as Ruby and Javascript a lot on the past. I am very familiar with both the capabilities people enjoy when using dynamism including a wide range of design techniques enabled by its use (Rails-like DSLs, etc). I am also very familiar with the disadvantages of dynamism, including member lookup bugs introduced by typos and refactoring / renaming.
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
In-depth consideration and participation in discussions on the list.