Pitch: Limit typealias extensions to the typealias

Makes sense. I guess from an implementation point of view, the compiler
only needs to be able to see the signatures of the members to synthesize
the calls to them. My thinking was that conforming the original type would
remove the need for the compiler to verify separately that the members
exist on the type (because of it conforms, they must, so you get it for
free), but I suppose in this case the compiler would just do a separate
validation pass.

I'm really looking forward to reading your draft!

···

On Fri, Jun 9, 2017 at 12:58 PM Matthew Johnson <matthew@anandabits.com> wrote:

On Jun 9, 2017, at 2:53 PM, Tony Allevato <tony.allevato@gmail.com> wrote:

+1 to this. My ideal imagining of this behavior is to:

1) Define a protocol containing the precise operations you want to support
via forwarding.
2) Internally/fileprivately extend the original type to conform to this
protocol.
3) In your new type, tell it to forward protocol members for a particular
property (the underlying String, or whatever) and the compiler will
synthesize the protocol member stubs on the new type.

There are definitely finer details to be worked out but that's a rough
sketch of how I've been imagining it could work. Nice advantages include
(1) if there already exists a protocol that defines the exact operations
you want to forward, you can skip step 2, and (2) it's explicit/opt-in
synthesis where you have to provide the members to forward (this is
expected, at a minimum), but you don't have to write all the boilerplate
implementations.

I have a mostly finished second draft of a protocol-oriented forwarding
proposal which is pretty similar to what you describe here. The most
important difference is that while protocols are used to perform forwarding
neither type is required to actually conform to the protocol.

That turns out to be pretty important when you think about edge cases.
Sometimes you don’t actually want the forwardee to conform (that’s probably
why you said internal / fileprivate). Sometimes you also don’t want the
forwarder to conform. IIRC, there are also cases where it cannot conform
(I would have to dig up the proposal to recall the details).

On Fri, Jun 9, 2017 at 12:47 PM Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

On Jun 9, 2017, at 2:39 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Interesting. So you’d want `newtype Foo = String` to start off with no
members on Foo?

Yeah. Previous discussions of newtype have usually led to discussion of
ways to forward using a protocol-oriented approach. Nothing has gotten too
far, but it usually comes up that suppressing undesired members is
important.

It is also important to have some way to distinguish between members with
a parameter of the underlying type from members that should be treated by
newtype as Self parameters. The mechanism we have for doing that in Swift
happens to be a protocol.

On Fri, Jun 9, 2017 at 15:18 Matthew Johnson <matthew@anandabits.com> >> wrote:

On Jun 9, 2017, at 12:09 PM, Xiaodi Wu via swift-evolution < >>> swift-evolution@swift.org> wrote:

On Fri, Jun 9, 2017 at 12:44 Robert Bennett via swift-evolution < >>> swift-evolution@swift.org> wrote:

Somewhat related to this, shouldn’t it be possible to sub-struct a
struct as long as you only add functions and computed properties (i.e., no
stored properties)? Traditionally structs cannot be subtyped because their
size must be known at compile time. I don’t know the implementation details
of where functions and computed properties live, but something tells me
they belong to the type and not the object (although I’ve never really made
the effort to sit down and fully understand Swift’s type model), in which
case adding them to a struct’s definition would not change the size of the
object on the stack. Thus it should be possible to make custom substructs
of String that add additional functionality but no new stored properties.
Thoughts?

Value subtyping is a large subject and, IIUC, newtype would be a subset
of that topic. Unlikely to be in scope for Swift 5, though, but that’s up
to the core team.

I see newtype as being more related to forwarding than subtyping.
Usually you want to hide significant parts of the interface to the wrapped
type.

On Jun 9, 2017, at 12:12 PM, Jacob Williams via swift-evolution < >>>> swift-evolution@swift.org> wrote:

On Jun 9, 2017, at 9:53 AM, Charlie Monroe <charlie@charliemonroe.net> >>>> wrote:

-1 - this would disallow e.g. to share UI code between iOS and macOS:

#if os(iOS)
typealias XUView = UIView
#else
typealias XUView = NSView
#endif

extension XUView {
...
}

I really don’t see how this disallows code sharing between the two
systems? Could you explain further? Based on my understanding of the pitch,
this is valid code still. (Although I do like the suggestion of a new
keyword rather than just limiting type alias).

Even if your example was invalid, you could also just do something like
this:

#if os(iOS)
typealias XUView = UIView
extension XUView {
//extension code here
}
#if os(macOS)
typealias XUView = UIView
extension XUView {
// extension code here
}
#endif

While not as pretty, still just as effective if you have to deal with
different types based on the system being compiled for and you could easily
still make the type alias extensions for each type work the same.

On Jun 9, 2017, at 9:53 AM, Charlie Monroe <charlie@charliemonroe.net> >>>> wrote:

-1 - this would disallow e.g. to share UI code between iOS and macOS:

#if os(iOS)
typealias XUView = UIView
#else
typealias XUView = NSView
#endif

extension XUView {
...
}

or with any similar compatibility typealiases.

On Jun 9, 2017, at 5:38 PM, Jacob Williams via swift-evolution < >>>> swift-evolution@swift.org> wrote:

+1 from me.

There have been times I’ve wanted to subclass an object (such as
String) but since it is a non-class, non-protocol type you can only extend
Strings existing functionality which adds that same functionality to
Strings everywhere. It would be nice if we could either extend type aliases
(and only the type alias), or if it were possible to inherit from structs
so that we could create a custom string type like so:

struct HeaderKey: String {
static var lastModified: String { return “Last-Modified” }
static var host: String { return “Host” }
}

I realize that struct inheritance is far less likely, since that
defeats one of the main pieces of what makes a struct a struct. So I’m all
for this proposal of allowing type aliases to be extended as though they
were their own struct/class.

Unfortunately, I’m not sure how feasible this kind of functionality
would actually be, but if it’s possible then I’m in favor of implementing
it.

On Jun 8, 2017, at 10:14 PM, Yvo van Beek via swift-evolution < >>>> swift-evolution@swift.org> wrote:

Typealiases can greatly reduce the complexity of code. But I think one
change in how the compiler handles them could make them even more powerful.

Let's say I'm creating a web server framework and I've created a simple
dictionary to store HTTP headers (I know that headers are more complex than
that, but as an example). I could write something like this:

    typealias HeaderKey = String

  var headers = [HeaderKey: String]()
  headers["Host"] = "domain.com"

Now I can define a couple of default headers like this:

  extension HeaderKey {
    static var lastModified: String { return "Last-Modified" }
    static var host: String { return "Host" }
  }

After that I can do this:

  var headers = [HeaderKey: String]()
  headers[.host] = "domain.com"
  headers[.lastModified] = "some date"
  headers["X-MyHeader"] = "This still works too"

But unfortunately the extension is also applied to normal strings:

    var normalString: String = .host

Perhaps it would be better if the extension would only apply to the
parts of my code where I use the HeaderKey typealias and not to all
Strings. This could be a great tool to specialize classes by creating a
typealias and adding functionality to it. Another example I can think of is
typealiases for dictionaries or arrays with added business logic through
extensions (especially since you can't inherit from structs).

If you want to create an extension that adds functionality to all
Strings you could have created an extension for String instead of HeaderKey.

Please let me know what you think. I'm not sure how complex this change
would be.
I could write a proposal if you're interested.

Kind regards,
Yvo
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________

swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Makes sense. I guess from an implementation point of view, the compiler only needs to be able to see the signatures of the members to synthesize the calls to them. My thinking was that conforming the original type would remove the need for the compiler to verify separately that the members exist on the type (because of it conforms, they must, so you get it for free), but I suppose in this case the compiler would just do a separate validation pass.

Yeah, conformance could be used as a shortcut when it exists but requiring it is an unnecessary limitation that reduces flexibility and actually prevents some uses altogether.

···

On Jun 9, 2017, at 3:16 PM, Tony Allevato <tony.allevato@gmail.com> wrote:

I'm really looking forward to reading your draft!
On Fri, Jun 9, 2017 at 12:58 PM Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jun 9, 2017, at 2:53 PM, Tony Allevato <tony.allevato@gmail.com <mailto:tony.allevato@gmail.com>> wrote:

+1 to this. My ideal imagining of this behavior is to:

1) Define a protocol containing the precise operations you want to support via forwarding.
2) Internally/fileprivately extend the original type to conform to this protocol.
3) In your new type, tell it to forward protocol members for a particular property (the underlying String, or whatever) and the compiler will synthesize the protocol member stubs on the new type.

There are definitely finer details to be worked out but that's a rough sketch of how I've been imagining it could work. Nice advantages include (1) if there already exists a protocol that defines the exact operations you want to forward, you can skip step 2, and (2) it's explicit/opt-in synthesis where you have to provide the members to forward (this is expected, at a minimum), but you don't have to write all the boilerplate implementations.

I have a mostly finished second draft of a protocol-oriented forwarding proposal which is pretty similar to what you describe here. The most important difference is that while protocols are used to perform forwarding neither type is required to actually conform to the protocol.

That turns out to be pretty important when you think about edge cases. Sometimes you don’t actually want the forwardee to conform (that’s probably why you said internal / fileprivate). Sometimes you also don’t want the forwarder to conform. IIRC, there are also cases where it cannot conform (I would have to dig up the proposal to recall the details).

On Fri, Jun 9, 2017 at 12:47 PM Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jun 9, 2017, at 2:39 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

Interesting. So you’d want `newtype Foo = String` to start off with no members on Foo?

Yeah. Previous discussions of newtype have usually led to discussion of ways to forward using a protocol-oriented approach. Nothing has gotten too far, but it usually comes up that suppressing undesired members is important.

It is also important to have some way to distinguish between members with a parameter of the underlying type from members that should be treated by newtype as Self parameters. The mechanism we have for doing that in Swift happens to be a protocol.

On Fri, Jun 9, 2017 at 15:18 Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jun 9, 2017, at 12:09 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Fri, Jun 9, 2017 at 12:44 Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Somewhat related to this, shouldn’t it be possible to sub-struct a struct as long as you only add functions and computed properties (i.e., no stored properties)? Traditionally structs cannot be subtyped because their size must be known at compile time. I don’t know the implementation details of where functions and computed properties live, but something tells me they belong to the type and not the object (although I’ve never really made the effort to sit down and fully understand Swift’s type model), in which case adding them to a struct’s definition would not change the size of the object on the stack. Thus it should be possible to make custom substructs of String that add additional functionality but no new stored properties. Thoughts?

Value subtyping is a large subject and, IIUC, newtype would be a subset of that topic. Unlikely to be in scope for Swift 5, though, but that’s up to the core team.

I see newtype as being more related to forwarding than subtyping. Usually you want to hide significant parts of the interface to the wrapped type.

On Jun 9, 2017, at 12:12 PM, Jacob Williams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jun 9, 2017, at 9:53 AM, Charlie Monroe <charlie@charliemonroe.net <mailto:charlie@charliemonroe.net>> wrote:

-1 - this would disallow e.g. to share UI code between iOS and macOS:

#if os(iOS)
  typealias XUView = UIView
#else
  typealias XUView = NSView
#endif

extension XUView {
  ...
}

I really don’t see how this disallows code sharing between the two systems? Could you explain further? Based on my understanding of the pitch, this is valid code still. (Although I do like the suggestion of a new keyword rather than just limiting type alias).

Even if your example was invalid, you could also just do something like this:

#if os(iOS)
  typealias XUView = UIView
  extension XUView {
    //extension code here
  }
#if os(macOS)
  typealias XUView = UIView
  extension XUView {
    // extension code here
  }
#endif

While not as pretty, still just as effective if you have to deal with different types based on the system being compiled for and you could easily still make the type alias extensions for each type work the same.

On Jun 9, 2017, at 9:53 AM, Charlie Monroe <charlie@charliemonroe.net <mailto:charlie@charliemonroe.net>> wrote:

-1 - this would disallow e.g. to share UI code between iOS and macOS:

#if os(iOS)
  typealias XUView = UIView
#else
  typealias XUView = NSView
#endif

extension XUView {
  ...
}

or with any similar compatibility typealiases.

On Jun 9, 2017, at 5:38 PM, Jacob Williams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

+1 from me.

There have been times I’ve wanted to subclass an object (such as String) but since it is a non-class, non-protocol type you can only extend Strings existing functionality which adds that same functionality to Strings everywhere. It would be nice if we could either extend type aliases (and only the type alias), or if it were possible to inherit from structs so that we could create a custom string type like so:

struct HeaderKey: String {
  static var lastModified: String { return “Last-Modified” }
  static var host: String { return “Host” }
}

I realize that struct inheritance is far less likely, since that defeats one of the main pieces of what makes a struct a struct. So I’m all for this proposal of allowing type aliases to be extended as though they were their own struct/class.

Unfortunately, I’m not sure how feasible this kind of functionality would actually be, but if it’s possible then I’m in favor of implementing it.

On Jun 8, 2017, at 10:14 PM, Yvo van Beek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Typealiases can greatly reduce the complexity of code. But I think one change in how the compiler handles them could make them even more powerful.

Let's say I'm creating a web server framework and I've created a simple dictionary to store HTTP headers (I know that headers are more complex than that, but as an example). I could write something like this:

    typealias HeaderKey = String

  var headers = [HeaderKey: String]()
  headers["Host"] = "domain.com <http://domain.com/>"

Now I can define a couple of default headers like this:

  extension HeaderKey {
    static var lastModified: String { return "Last-Modified" }
    static var host: String { return "Host" }
  }

After that I can do this:

  var headers = [HeaderKey: String]()
  headers[.host] = "domain.com <http://domain.com/>"
  headers[.lastModified] = "some date"
  headers["X-MyHeader"] = "This still works too"

But unfortunately the extension is also applied to normal strings:

    var normalString: String = .host

Perhaps it would be better if the extension would only apply to the parts of my code where I use the HeaderKey typealias and not to all Strings. This could be a great tool to specialize classes by creating a typealias and adding functionality to it. Another example I can think of is typealiases for dictionaries or arrays with added business logic through extensions (especially since you can't inherit from structs).

If you want to create an extension that adds functionality to all Strings you could have created an extension for String instead of HeaderKey.

Please let me know what you think. I'm not sure how complex this change would be.
I could write a proposal if you're interested.

Kind regards,
Yvo
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Has this pitch just dried up or was it discussed somewhere else?
With Swift 6 nearing the doorstep, I'd like to reanimate this topic.

I'd welcome something like the above mentioned newtype, which disallows implicit conversion and does not apply extensions of other "typealiases" to the newly defined type.

I've seen projects where I could convert a Double with toFahrenheit() and toCelsius(), even though the original type was a typealias DegreeCelsius = Double ...

+1 for the newtype. I'll call it here typedef and here is another use case besides the extensions.

protocol Object {
    associatedtype Id
    var id: Id { get }
}

struct A: Object {
    typedef Id = String
    let id: Id
}

struct B: Object {
    typedef Id = String
    let id: Id
}

With typealias since typedef doesn't exist yet, these are the problems that I often need to fix with arbitrary wrappers around String.

A.Id.self == B.Id.self // Not what I want.
func takeAId(_ aId: A.Id) // Takes any String.

even a shorter word?

typedef --> type

:slight_smile:

1 Like

+1 for the idea, but since the aliased type is not the original one any more, typedef ID = String could make some confusion.

I would propose typedef ID: String { /* custom extension */ } instead, yet there are still bunch of problems to be solved:

  • Should the new ID type fit in String? How about using String when ID is required?
  • What about the visibility of String's properties and methods?
  • How can we convert between String and ID?
  • Which of the protocols can ID gain effortlessly?

To be clear, I still consider this to be a plain syntactic sugar around static-typed @dynamicMemberLookup, so it just reduces code and structure complexity, but doesn’t unlock any new functionality from my sight.