[swift-evolution] [Proposal] Explicit Synthetic Behaviour

David Sweeris davesweeris at mac.com
Wed Sep 13 19:41:19 CDT 2017

> On Sep 12, 2017, at 8:07 PM, Tony Allevato via swift-evolution <swift-evolution at swift.org> wrote:
> But all that stuff about custom attributes and metaprogramming introspection is a big topic of it's own that isn't going to be solved in Swift 5, so this is a bit of a digression. :)

Is it really a digression, though? Seems like with source-compatibility being essentially required going forward, it's important to nail this stuff down sooner rather than later if we want a nice, consistent language for The Future™.

I mean, the idea of writing "@adjective var noun: Type" to indicate that a certain variable shouldn't take place in code synthesis seems fairly safe to me, but proving $Idea1 is generalizable without stepping on $Idea2 is outside my area of expertise.

> On Sep 12, 2017, at 8:07 PM, Tony Allevato via swift-evolution <swift-evolution at swift.org> wrote:
> On Tue, Sep 12, 2017 at 7:10 PM Xiaodi Wu <xiaodi.wu at gmail.com <mailto:xiaodi.wu at gmail.com>> wrote:
> On Tue, Sep 12, 2017 at 9:58 AM, Thorsten Seitz via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> Good arguments, Tony, you have convinced me on all points. Transient is the way to go. Thank you for your patience!
> On many points, I agree with Tony, but I disagree that "transient" addresses the issue at hand. The challenge being made is that, as Gwendal puts it, it's _unwise_ to have a default implementation, because people might forget that there is a default implementation. "Transient" only works if you remember that there is a default implementation, and in that case, we already have a clear syntax for overriding the default.
> Right—I hope it hasn't sounded like I'm conflating the two concepts completely. The reason I brought up "transient" is because nearly all of the "risky" examples being cited so far have been of the variety "I have a type where some properties happen to be Equatable but shouldn't be involved in equality", so my intention has been to show that if we have a better solution to that specific problem (which is, related to but not the same as the question at hand), then there aren't enough risky cases left to warrant adding this level of complexity to the protocol system.
> As others point out, there's a temptation here to write things like "transient(Equatable)" so as to control the synthesis of implementations on a per-protocol basis. By that point, you've invented a whole new syntax for implementing protocol requirements. (Ah, you might say, but it's hard to write a good hashValue implementation: sure, but that's adequately solved by a library-supplied combineHashes() function.)
> I totally agree with this. A design that would try to annotate "transient" with a protocol or list of protocols is missing the point of the semantics that "transient" is supposed to provide. It's not a series of switches to that can be flipped on and off for arbitrary protocols—it's a semantic tag that assigns additional meaning to properties and certain protocols (such as Equatable, Hashable, and Codable, but possibly others that haven't been designed yet) would have protocol-specific behavior for those properties.
> To better explain what I've been poking at, I'm kind of extrapolating this out to a possible future where it may be possible to more generally (1) define custom @attributes in Swift, like Java annotations, and then (2) use some metaprogramming constructs to generate introspective default implementations for a protocol at compile-time just as the compiler does "magically" now, and the generator would be able to query attributes that are defined by the same library author as the protocol and handle them accordingly.
> In a world where that's possible, I think it's less helpful to think in terms of "I need to distinguish between conforming to X and getting a synthesized implementation and conforming to X and avoiding the synthesized implementation because the default might be risky", but instead to think in terms of "How can I provide enough semantic information about my types to remove the risk?"
> In other words, the switches we offer developers to flip shouldn't be about turning on/off entire features, but about giving the compiler enough information to make it smart enough that we never need to turn it off in the first place. As I alluded to before, if I have 10 properties in a type and only 1 of those needs to be ignored in ==/hashValue/whatever, writing "Equatable" instead of "derives Equatable" isn't all that helpful. Yes, it spits out an error message where there wouldn't have been one, but it doesn't reduce any of the burden of having to provide the appropriate manual implementation.

Speaking of which, what do you suppose the hit/miss ratio would be WRT synthesized `Equatable`, etc, if we introduced "trivial" or "simple" types ("trivial struct Foo {}", "trivial class Bar {}") -- meaning that all the stored properties (or associated values, for enums) are either all trivial value types or trivial reference types -- and only performing the code code synthesis for such trivial types? We'd probably need some mechanism of telling the compiler that for this purpose, a struct counts as a reference type (or the other way around)... mostly I'm thinking that the Unsafe*Pointer types would break the semantic contract, since they kinda have both (from a certain point of view).

trivial struct Foo : Equatable { // both x and y are value semantics all the way down, so "==" can be synthesized
 var x: Int
 var y: Int
struct FooWithHistory : Equatable { // Arrays use references under the covers; this can't be `trivial`, nothing is synthesized, and this wouldn't compile without the user supplying a `==` function
  var x: Int { didSet { xHistory.append(oldValue) } }
  var y: Int { didSet { yHistory.append(oldValue) } }
  var xHistory: [Int] = []
  var yHistory: [Int] = []

(I tried to come up with a simple example that was all references "all the way down", but you've eventually gotta answer the question "a reference to what?", at which point the type could no longer be "trivial", so I'm not sure that's possible, at least in a useful manner. I suppose maybe the "bottom" property could be a pointer... that might do it...)

It seems to me that there might be implications here for auto-parallelization, too. If so, this would be a great time in Swift's evolution (😃) to explore the idea.

- Dave Sweeris
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170913/02d1a8ea/attachment.html>

More information about the swift-evolution mailing list