SE-0005: Ambiguous NS prefix removals


(Brent Royal-Gordon) #1

SE-0005 in the repository (“Better Translation of Objective-C APIs into Swift”, https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md) discusses plans to remove the NS prefix from many Foundation APIs, but admits that you haven’t yet settled on a way to handle conflicts with the standard library:

The removal of the "NS" prefix for the Foundation module (or other specifically identified modules) is a mechanical translation for all global symbols defined within that module that can be performed in the Clang importer. Note that this removal can create conflicts with the standard library. For example, NSString and NSArray will become String and Array, respectively, and Foundation's versions will shadow the standard library's versions. We are investigating several ways to address this problem, including:

  • Retain the NS prefix on such classes.

  • Introduce some notion of submodules into Swift, so that these classes would exist in a submodule for reference-semantic types (e.g., one would refer to Foundation.ReferenceTypes.Array or similar).

Since the main difference between e.g. String and NSString is that the latter has reference semantics, why not alter the name to reflect that?

  NSString -> StringRef
  NSArray -> ArrayRef
  NSDictionary -> DictionaryRef

The resulting type names have a bit of a Core Foundation-y flavor to them, but that’s not necessarily a bad thing—it quietly encourages use of the Swift types just as NSString vs. CFStringRef quietly encourages use of the Foundation type.

Normally this transformation would not apply to types with no corresponding standard library type:

  NSCalendar -> Calendar
  NSBundle -> Bundle

However, subclasses of a type with “Ref” in the name would also have “Ref" in the subclass name:

  NSMutableString -> MutableStringRef (because its superclass is StringRef)
  NSMutableArray -> MutableArrayRef (because its superclass is ArrayRef)
  NSMutableDictionary -> MutableDictionaryRef (because its superclass is DictionaryRef)

If you don’t like “Ref”, “Object” is wordier but clearer:

  StringObject, MutableStringObject
  ArrayObject, MutableArrayObject
  DictionaryObject, MutableDictionaryObject

And as long as we’re doing this, it might make sense to add the “Ref” suffix for a few other types which might naturally have value-typed standard library equivalents someday, even if they don’t right now:

  URLRef (the URL type would have something like NSURLComponents’s mutation APIs)
  DateComponentsRef (this is begging to be a struct in Swift)
  DateRef (perhaps less important, since it’s immutable and doesn’t really have many natural properties to expose anyway)
  NumberRef (a standard library equivalent might be a protocol that all numeric types conform to)

On the other hand, you might just want to cross that bridge when you get to it with a source code migrator in Swift 4 or whatever.

(By the way, I love the rest of this proposal. I’ve always wanted Swift to clean up and reformat API names more when it imports them.)

···

--
Brent Royal-Gordon
Architechies


(Tony Parker) #2

Hi Brent,

SE-0005 in the repository (“Better Translation of Objective-C APIs into Swift”, https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md) discusses plans to remove the NS prefix from many Foundation APIs, but admits that you haven’t yet settled on a way to handle conflicts with the standard library:

The removal of the "NS" prefix for the Foundation module (or other specifically identified modules) is a mechanical translation for all global symbols defined within that module that can be performed in the Clang importer. Note that this removal can create conflicts with the standard library. For example, NSString and NSArray will become String and Array, respectively, and Foundation's versions will shadow the standard library's versions. We are investigating several ways to address this problem, including:

  • Retain the NS prefix on such classes.

  • Introduce some notion of submodules into Swift, so that these classes would exist in a submodule for reference-semantic types (e.g., one would refer to Foundation.ReferenceTypes.Array or similar).

Since the main difference between e.g. String and NSString is that the latter has reference semantics, why not alter the name to reflect that?

  NSString -> StringRef
  NSArray -> ArrayRef
  NSDictionary -> DictionaryRef

The resulting type names have a bit of a Core Foundation-y flavor to them, but that’s not necessarily a bad thing—it quietly encourages use of the Swift types just as NSString vs. CFStringRef quietly encourages use of the Foundation type.

Normally this transformation would not apply to types with no corresponding standard library type:

  NSCalendar -> Calendar
  NSBundle -> Bundle

However, subclasses of a type with “Ref” in the name would also have “Ref" in the subclass name:

  NSMutableString -> MutableStringRef (because its superclass is StringRef)
  NSMutableArray -> MutableArrayRef (because its superclass is ArrayRef)
  NSMutableDictionary -> MutableDictionaryRef (because its superclass is DictionaryRef)

If you don’t like “Ref”, “Object” is wordier but clearer:

  StringObject, MutableStringObject
  ArrayObject, MutableArrayObject
  DictionaryObject, MutableDictionaryObject

And as long as we’re doing this, it might make sense to add the “Ref” suffix for a few other types which might naturally have value-typed standard library equivalents someday, even if they don’t right now:

  URLRef (the URL type would have something like NSURLComponents’s mutation APIs)
  DateComponentsRef (this is begging to be a struct in Swift)
  DateRef (perhaps less important, since it’s immutable and doesn’t really have many natural properties to expose anyway)
  NumberRef (a standard library equivalent might be a protocol that all numeric types conform to)

On the other hand, you might just want to cross that bridge when you get to it with a source code migrator in Swift 4 or whatever.

We’ve been thinking about exactly what to call this submodule, but haven’t landed on a preferred name yet. For the class names themselves, I don’t think we want to suffix some classes with ‘Ref’ or ‘Object’ but not others, because it would lead to either boilerplate names or inconsistency. The idea of the submodule was to avoid the inconsistency but still have something which obviously separates these classes.

e.g.

NSString -> submodule.String
NSBundle -> Bundle
NSMutableString -> submodule.MutableString
NSArray -> submodule.Array

etc.

- Tony

···

On Dec 4, 2015, at 3:50 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

(By the way, I love the rest of this proposal. I’ve always wanted Swift to clean up and reformat API names more when it imports them.)

--
Brent Royal-Gordon
Architechies

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


(Brent Royal-Gordon) #3

We’ve been thinking about exactly what to call this submodule, but haven’t landed on a preferred name yet. For the class names themselves, I don’t think we want to suffix some classes with ‘Ref’ or ‘Object’ but not others, because it would lead to either boilerplate names or inconsistency. The idea of the submodule was to avoid the inconsistency but still have something which obviously separates these classes.

But I’m not sure using a submodule really solves either of these problems.

BOILERPLATE: How are you going to address types inside the module? Either you’ll have to always include the prefix—in which case the prefix is boilerplate, but it’s in the wrong place on the type name to read correctly (“reference array” instead of “array reference”) and requires an extra character due to the “.”—or there will be some way to make Swift favor the reference types over the value types, in which case you won’t even be able to read “Array” or “String” in a piece of code without wondering which semantics you’re talking about.

INCONSISTENCY: Not every type in Foundation will be moved into this submodule; some will stay behind in the top-level Foundation module. Doesn’t that mean the submodule approach is inconsistent too?

So is putting these types into a submodule actually fixing these problems, or is it just sticking a dot after them?

Actually, as long as we’re here, here’s a weird idea I just came up with. Why not nest the reference types inside the types they bridge to?

  NSString -> String.Reference
  NSMutableString -> String.MutableReference
  NSArray<T> -> Array<T>.Reference
  NSMutableArray<T> -> Array<T>.MutableReference

This would require you to get a handle on nesting types within generic types, but personally, that’s always been a pain point for me in Swift’s design. With the right type resolution rules, it might also allow you to say something like this and let Swift figure out what you mean:

  let foo = “foo bar baz” as .Reference // implicitly means String.Reference

And if this was driven by a protocol with a typealias for the reference type, you could even make this public so users who have their own matching value/reference types can have them autoboxed:

  // Think of this as a cleaned up version of _ObjectiveCBridgeable.
  public protocol Referable {
    typealias Reference: class
    static func referenceType() -> Reference.Type // or make it possible to get this from the protocol witness
    
    var reference: Reference { get }
    init?(reference: Reference) // for an as! conversion, this is force-unwrapped by the caller
  }

···

--
Brent Royal-Gordon
Architechies


(Tony Parker) #4

We’ve been thinking about exactly what to call this submodule, but haven’t landed on a preferred name yet. For the class names themselves, I don’t think we want to suffix some classes with ‘Ref’ or ‘Object’ but not others, because it would lead to either boilerplate names or inconsistency. The idea of the submodule was to avoid the inconsistency but still have something which obviously separates these classes.

But I’m not sure using a submodule really solves either of these problems.

BOILERPLATE: How are you going to address types inside the module? Either you’ll have to always include the prefix—in which case the prefix is boilerplate, but it’s in the wrong place on the type name to read correctly (“reference array” instead of “array reference”) and requires an extra character due to the “.”—or there will be some way to make Swift favor the reference types over the value types, in which case you won’t even be able to read “Array” or “String” in a piece of code without wondering which semantics you’re talking about.

You’re right that there is going to be some boilerplate here. Inside the Foundation module we’re willing to accept the readability hit of using submodule.Array when we mean “NSArray.” Using NSArray outside of Foundation itself should be relatively rare compared to using the standard library Array, so it should not be very prevalent there.

The idea we’ve been thinking about is to add something (in the module map, or in the class interface itself) here which makes the submodule specification required. We don’t have that capability today, so we need a compiler change of some kind to make this work if it is the direction we go. That means that Array always means Array.

INCONSISTENCY: Not every type in Foundation will be moved into this submodule; some will stay behind in the top-level Foundation module. Doesn’t that mean the submodule approach is inconsistent too?

It would just be for types which have a value type equivalent in Swift that we would rather be used in most situations. This is why NSString Obj-C API is imported into Swift API as String, for example.

So is putting these types into a submodule actually fixing these problems, or is it just sticking a dot after them?

Actually, as long as we’re here, here’s a weird idea I just came up with. Why not nest the reference types inside the types they bridge to?

  NSString -> String.Reference
  NSMutableString -> String.MutableReference
  NSArray<T> -> Array<T>.Reference
  NSMutableArray<T> -> Array<T>.MutableReference

This would require you to get a handle on nesting types within generic types, but personally, that’s always been a pain point for me in Swift’s design. With the right type resolution rules, it might also allow you to say something like this and let Swift figure out what you mean:

  let foo = “foo bar baz” as .Reference // implicitly means String.Reference

And if this was driven by a protocol with a typealias for the reference type, you could even make this public so users who have their own matching value/reference types can have them autoboxed:

  // Think of this as a cleaned up version of _ObjectiveCBridgeable.
  public protocol Referable {
    typealias Reference: class
    static func referenceType() -> Reference.Type // or make it possible to get this from the protocol witness
    
    var reference: Reference { get }
    init?(reference: Reference) // for an as! conversion, this is force-unwrapped by the caller
  }

--
Brent Royal-Gordon
Architechies

This is an interesting idea; although I think it goes beyond what we had in mind for just the renaming. Even without the casting idea you propose, the names are not too different from calling the submodule ‘Reference’, which is something we considered:

NSString -> Reference.String
NSMutableString -> Reference.MutableString
NSArray -> Reference.Array

etc. The problem with this in my mind is that ‘Reference’ is not a good enough word to describe the difference between Foundation.NSString and Swift.String.

- Tony

···

On Dec 4, 2015, at 12:09 PM, Brent Royal-Gordon <brent@architechies.com> wrote:


(Nikolai Vazquez) #5

Hey Brent,

I really like the idea you proposed with having a Reference nested type. I
prefer a name like Object but that's just me.

From my experience, nested types don't conflict with typealiases for

protocol conformance so you could do something similar to:

extension Array: Referable {
    class Reference<Element> { ... }
    var reference: Reference<Element> { ... }
}

I do agree with Tony however that types like NSBundle that have no Swift
counterpart could be renamed to simply Bundle.

One thing I'm wondering about is abuse of the Referable protocol. It could
lead to developers feeling the need to conform to it when it comes to
creating new value types. However, it shouldn't be an issue.

···

On Fri, Dec 4, 2015, 3:09 PM Brent Royal-Gordon <brent@architechies.com> wrote:

> We’ve been thinking about exactly what to call this submodule, but
haven’t landed on a preferred name yet. For the class names themselves, I
don’t think we want to suffix some classes with ‘Ref’ or ‘Object’ but not
others, because it would lead to either boilerplate names or inconsistency.
The idea of the submodule was to avoid the inconsistency but still have
something which obviously separates these classes.

But I’m not sure using a submodule really solves either of these problems.

BOILERPLATE: How are you going to address types inside the module? Either
you’ll have to always include the prefix—in which case the prefix is
boilerplate, but it’s in the wrong place on the type name to read correctly
(“reference array” instead of “array reference”) and requires an extra
character due to the “.”—or there will be some way to make Swift favor the
reference types over the value types, in which case you won’t even be able
to read “Array” or “String” in a piece of code without wondering which
semantics you’re talking about.

INCONSISTENCY: Not every type in Foundation will be moved into this
submodule; some will stay behind in the top-level Foundation module.
Doesn’t that mean the submodule approach is inconsistent too?

So is putting these types into a submodule actually fixing these problems,
or is it just sticking a dot after them?

Actually, as long as we’re here, here’s a weird idea I just came up with.
Why not nest the reference types inside the types they bridge to?

        NSString -> String.Reference
        NSMutableString -> String.MutableReference
        NSArray<T> -> Array<T>.Reference
        NSMutableArray<T> -> Array<T>.MutableReference

This would require you to get a handle on nesting types within generic
types, but personally, that’s always been a pain point for me in Swift’s
design. With the right type resolution rules, it might also allow you to
say something like this and let Swift figure out what you mean:

        let foo = “foo bar baz” as .Reference // implicitly means
String.Reference

And if this was driven by a protocol with a typealias for the reference
type, you could even make this public so users who have their own matching
value/reference types can have them autoboxed:

        // Think of this as a cleaned up version of _ObjectiveCBridgeable.
        public protocol Referable {
                typealias Reference: class
                static func referenceType() -> Reference.Type
     // or make it possible to get this from the protocol witness

                var reference: Reference { get }
                init?(reference: Reference)
                     // for an as! conversion, this is force-unwrapped by
the caller
        }

--
Brent Royal-Gordon
Architechies

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


(Rudolf Adamkovič) #6

The problem with this in my mind is that ‘Reference’ is not a good enough word to describe the difference between Foundation.NSString and Swift.String.

- Tony

What about Legacy instead of Reference?

P.S. Hopefully, UI and other prefixes will be removed "soonish" too.

R+

···

Sent from my iPhone

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