Syntactic protocols (cf. function builders)

Take property wrappers. The "syntactic protocol" for property wrappers looks something like this:

syntactic protocol PropertyWrapper {
  var wrappedValue<Value>: Value { get set }
  init<Value>(wrappedValue: Value, *)
  init(*)
  // I forgot projectedValue, but let's not worry about it.
}

And the use site looks like this:

struct Example {
  @A var a: Int // uses wrappedValue and init()
  @B(5) var b: Int // uses wrappedValue and init(_:)
  @C var c: Int = 1 // uses wrappedValue and init(wrappedValue:)
  @D(5) var d: Int = 1 // uses wrappedValue and init(wrappedValue:_:)
}

struct Example2 {
  @E var e: Int // uses wrappedValue only
  init() {
    self._e = E.default
  }
}

But the syntactic protocol doesn't describe how to transform those use sites into uses of the protocol. Indeed, there's a lot more transformation than just uses of the protocol; the feature has to declare new properties and provide getters and setters for the existing ones. You could imagine a macro system powerful enough to do all that, but I deliberately didn't propose it here.

Those don't actually have any syntactic requirements! They need access to all of a struct's fields or enum's cases, and they have restrictions on the forms of those cases, but there's nothing where the compiler has to construct a call that isn't already part of a protocol.

As you both point out, though, default implementations with access to members is absolutely a useful feature. I just don't think it's going to be this feature.

I may not have clearly communicated what I meant. I may not have the sufficient level of understanding to do so. I wasnā€™t necessarily saying that the default implementations themselves needed to live in the syntactic definitions, but that there should be some kind of marker identifying the existence of compiler magic tied to a given protocol.

Is this referring to being able to define default implementations inside the curly braces of the protocol block instead of in an extension? (I do not understand why this isnā€™t allowed in the first place. If you can extend it to add the functionality, why canā€™t it be included in the original definition?)

I think the ultimate goal is to one day be able to take these out of the compiler and write them in swift (maybe using some kind of static reflection). At that point theyā€™ll be just like regular default implementations on protocols.

5 Likes

That's fair. As @Karl said, ideally in Swift 17 these will all be things the language can do, but we can look for default implementations provided in the languageā€”they're in protocol extensions. There's no indication of a default implementation that comes from the compiler.

No, I was referring to the fact that synthesizing, say, == requires a way to iterate over all of a struct's fields (ideally at compile time, not run time, so that the comparisons can be optimized or at least inlined). That's another feature that people want for various purposes, but it's not something that syntactic protocols helps with.

That was the original plan, actually :-) but it doesn't support having multiple default implementations based on what other protocols a type implements. Because that turned out to be useful to support, the unconstrained extension case was just as easy to support, and then there wasn't nearly as much reason to support writing it another way (inline in the protocol body). That said, there is one language-level motivation for doing so: adding a new requirement is only source-compatible if you have an unconstrained default implementation for it, and it would be simpler to just look for that default implementation in the main protocol body rather than checking extensions. So it could still happen at some point.

(That's not strictly how the feature evolved, but it's close enough to explain why there's never been movement on allowing implementations in the protocol body.)

2 Likes