[Proposal] Revamp the playground quicklook APIs

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

···

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its representation in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate representations for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The new CustomPlaygroundRepresentable protocol would allow instances to return an alternate object or value (as an Any) which would serve as their representation. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundRepresentable protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate representation for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundRepresentable instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

/// A type that supplies a custom representation for playground logging.
///
/// All types have a default representation for playgrounds. This protocol
/// allows types to provide custom representations which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default representation is preferable.
///
/// Playground logging can generate, at a minimum, a structured representation
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized representation of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// representations of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized representations
/// of other types.
///
/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {
  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging. (By returning an Any?, it also allows instances to opt-in to their standard playground representation if that is preferable some cases.)

Implementations of CustomPlaygroundRepresentable may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "MyStruct representation"
  }
}

extension MyOtherStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct representation" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundRepresentable. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate toCustomPlaygroundRepresentable. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundRepresentable if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundRepresentable to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground representations. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide representations as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred representation. (That is, what would PlaygroundLogger select as the representation of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygroundrepresentable-in-the-standard-library>Implement CustomPlaygroundRepresentable in the standard library

As an alternative to implementing CustomPlaygroundRepresentable in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundRepresentable in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundRepresentable protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygroundrepresentable-return-something-other-than-any>Have CustomPlaygroundRepresentable return something other than Any?

One minor alternative considered was to have CustomPlaygroundRepresentable return a value with a more specific type than Any?. For example:

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: CustomPlaygroundRepresentable? { get }
}
or:

protocol PlaygroundRepresentation {}

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: PlaygroundRepresentation? { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundRepresentation.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)

Big +1 to this. Getting these types out of the standard library and into a more suitable domain-specific framework, prior to declaring ABI Stability, will give us flexibility to evolve them appropriately over time.

···

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its representation in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate representations for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The new CustomPlaygroundRepresentable protocol would allow instances to return an alternate object or value (as an Any) which would serve as their representation. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundRepresentable protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate representation for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundRepresentable instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

/// A type that supplies a custom representation for playground logging.
///
/// All types have a default representation for playgrounds. This protocol
/// allows types to provide custom representations which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default representation is preferable.
///
/// Playground logging can generate, at a minimum, a structured representation
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized representation of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// representations of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized representations
/// of other types.
///
/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {
  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging. (By returning an Any?, it also allows instances to opt-in to their standard playground representation if that is preferable some cases.)

Implementations of CustomPlaygroundRepresentable may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "MyStruct representation"
  }
}

extension MyOtherStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct representation" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundRepresentable. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate toCustomPlaygroundRepresentable. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundRepresentable if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundRepresentable to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground representations. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide representations as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred representation. (That is, what would PlaygroundLogger select as the representation of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygroundrepresentable-in-the-standard-library>Implement CustomPlaygroundRepresentable in the standard library

As an alternative to implementing CustomPlaygroundRepresentable in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundRepresentable in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundRepresentable protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygroundrepresentable-return-something-other-than-any>Have CustomPlaygroundRepresentable return something other than Any?

One minor alternative considered was to have CustomPlaygroundRepresentable return a value with a more specific type than Any?. For example:

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: CustomPlaygroundRepresentable? { get }
}
or:

protocol PlaygroundRepresentation {}

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: PlaygroundRepresentation? { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundRepresentation.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’ve just glanced through this, so I apologize if this was already addressed, but will the default behavior (i.e. that of something that doesn’t conform to CustomPlaygroundRepresentable) remain the same?

Saagar Jha

···

On Jan 9, 2018, at 15:19, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its representation in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate representations for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The new CustomPlaygroundRepresentable protocol would allow instances to return an alternate object or value (as an Any) which would serve as their representation. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundRepresentable protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate representation for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundRepresentable instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

/// A type that supplies a custom representation for playground logging.
///
/// All types have a default representation for playgrounds. This protocol
/// allows types to provide custom representations which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default representation is preferable.
///
/// Playground logging can generate, at a minimum, a structured representation
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized representation of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// representations of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized representations
/// of other types.
///
/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {
  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging. (By returning an Any?, it also allows instances to opt-in to their standard playground representation if that is preferable some cases.)

Implementations of CustomPlaygroundRepresentable may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "MyStruct representation"
  }
}

extension MyOtherStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct representation" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundRepresentable. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate toCustomPlaygroundRepresentable. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundRepresentable if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundRepresentable to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground representations. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide representations as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred representation. (That is, what would PlaygroundLogger select as the representation of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygroundrepresentable-in-the-standard-library>Implement CustomPlaygroundRepresentable in the standard library

As an alternative to implementing CustomPlaygroundRepresentable in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundRepresentable in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundRepresentable protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygroundrepresentable-return-something-other-than-any>Have CustomPlaygroundRepresentable return something other than Any?

One minor alternative considered was to have CustomPlaygroundRepresentable return a value with a more specific type than Any?. For example:

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: CustomPlaygroundRepresentable? { get }
}
or:

protocol PlaygroundRepresentation {}

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: PlaygroundRepresentation? { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundRepresentation.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Good afternoon,

Hi Connor,

Huge +1 for this proposal, I’m thrilled you’re cleaning this up. Couple of detail questions:

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>
Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

What is the use-case for a type conforming to this protocol but returning nil? If there is a use case for that, why not have such an implementation return “self” instead?

In short, can we change playgroundRepresentation to return Any instead of Any?. Among other things, doing so could ease the case of playground formatting Optional itself, which should presumably get a conditional conformance to this. :slight_smile:

/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {

On the naming bikeshed, the closest analog to this feature is CustomStringConvertible, which is used when a type wants to customize the default conversion to string. As such, have you considered CustomPlaygroundConvertible for consistency with it?

The only prior art for the word “Representable” in the standard library is RawRepresentable, which is quite a different concept.

  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }

Again to align with CustomStringConvertible which has a ‘description’ member, it might make sense to name this member “playgroundDescription”.

Thank you again for pushing this forward, this will be much cleaner!

-Chris

···

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:

Something like this was attempted (and reverted) for Swift 3:

<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160808/026088.html>

<https://github.com/apple/swift-evolution/commit/e2610e3fa91b437e06e768aaef6820d755489717>

<https://github.com/apple/swift-xcode-playground-support/commit/0f42ade5a6302cf953a3ed32a892f23e8e150c62>

Is it now possible to `import PlaygroundSupport` outside of a playground?

-- Ben

···

On 9 Jan 2018, at 23:19, Connor Wakamo wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

I was always surprised that "Playground" was a word/concept that the
Standard Library used, as playgrounds are an Xcode feature and iOS app
which are not part of the Swift open source project.

I think it might be better if names for these could be found which
reflect the functionality they provide, in a non "playground" specific
way. After all, AFAIK it would be possible for non-"playground" uses
of CustomPlaygroundRepresentable.

···

On 9 January 2018 at 23:19, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:

To that end, I am proposing the following:

- Introduce a new protocol, CustomPlaygroundRepresentable, in the
PlaygroundSupport library in Swift 4.1:

protocol CustomPlaygroundRepresentable {
/// Returns an alternate object or value which should stand in for the
receiver in playground logging, or nil if the receiver’s default
representation is preferred.
var playgroundRepresentation: Any? { get }
}

--
Ian Partridge

Thanks to everyone who’s commented on this proposal. I’ve updated it with feedback from this round of discussion, and I plan to submit a PR to the swift-evolution repo with this proposal to start the official review process in the next couple of days (as I finish up the implementation of this proposal).

Since this mailing list will be offline later today for the transition to Discourse, please let me know directly if you have any feedback before the transition is complete and I’ll take it into account.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its description in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate descriptions for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The newCustomPlaygroundDisplayConvertible protocol would allow instances to return an alternate object or value (as an Any) which would serve as their description. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20180108/042639.html>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundDisplayConvertible protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate description for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundDisplayConvertible instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

/// A type that supplies a custom description for playground logging.
///
/// All types have a default description for playgrounds. This protocol
/// allows types to provide custom descriptions which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default description is preferable.
///
/// Playground logging can generate, at a minimum, a structured description
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized description of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// descriptions of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized descriptions
/// of other types.
///
/// Implementors of `CustomPlaygroundDisplayConvertible` may return a value of
/// one of the above types to also receive a specialized log description.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundDisplayConvertible {
  /// Returns the custom playground description for this instance.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundDescription: Any { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging.

Implementations of CustomPlaygroundDisplayConvertible may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "MyStruct description for playgrounds"
  }
}

extension MyOtherStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct description for playgrounds" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundDisplayConvertible. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate to CustomPlaygroundDisplayConvertible. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundDisplayConvertible if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundDisplayConvertible to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground descriptions. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide descriptions as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred description. (That is, what would PlaygroundLogger select as the description of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygrounddisplayconvertible-in-the-standard-library>Implement CustomPlaygroundDisplayConvertible in the standard library

As an alternative to implementing CustomPlaygroundDisplayConvertible in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundDisplayConvertible in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundDisplayConvertible protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-something-other-than-any>Have CustomPlaygroundDisplayConvertible return something other than Any

One minor alternative considered was to have CustomPlaygroundDisplayConvertible return a value with a more specific type than Any. For example:

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: CustomPlaygroundDisplayConvertible { get }
}
or:

protocol PlaygroundDescription {}

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: PlaygroundDescription { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundDescription.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-an-any-instead-of-an-any>Have CustomPlaygroundDisplayConvertible return an Any? instead of an Any

One alternative considered was to have CustomPlaygroundDisplayConvertible return an Any? instead of an Any. This would permit individual instances to opt-out of a custom playground description by returning nil instead of a concrete value or object.

Although that capability is no longer present, in most cases implementors of CustomPlaygroundDisplayConvertiblemay return a custom description which closely mirrors their default description. One big exception to this are classes which are considered core types, such as NSView and UIView, as one level of subclass may wish to customize its description while deeper level may wish to use the default description (which is currently a rendered image of the view). This proposal does not permit that; the second-level subclass must return a custom description one way or another, and due to the chaining nature of CustomPlaygroundDisplayConvertible implementations, it cannot return self and have that reliably indicate to the playground logger implementation that that means "don't use a custom description".

This issue seems to be limited enough that it should not tarnish the API design as a whole. Returning Any and not Any?is easier to understand, so this proposal opts to do that. Should this be a larger issue than anticipated, a future proposal could introduce a struct like DefaultPlaygroundDescription<T> which the playground logger would understand to mean "don't check for a CustomPlaygroundDisplayConvertible conformance on the wrapped value".

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternate-names-for-customplaygrounddisplayconvertible>Alternate Names for CustomPlaygroundDisplayConvertible

Finally, as this introduces a new protocol, there are other possible names:

CustomPlaygroundRepresentable
CustomPlaygroundConvertible
CustomPlaygroundPreviewConvertible
CustomPlaygroundQuickLookConvertible
CustomPlaygroundValuePresentationConvertible
CustomPlaygroundPresentationConvertible
CustomPlaygroundRepresentable was rejected as it does not match the naming convention established byCustomStringConvertible/CustomDebugStringConvertible. CustomPlaygroundConvertible was rejected as not being specific enough -- types conforming to this protocol are not themselves convertible to playgrounds, but are instead custom convertible for playground display. CustomPlaygroundPreviewConvertible is very similar toCustomPlaygroundDisplayConvertible, but implies more about the presentation than is appropriate as a playground environment is free to display it any way it wants, not just as a "preview". CustomPlaygroundQuickLookConvertible was rejected as it potentially invokes the to-be-removed PlaygroundQuickLook enum.CustomPlaygroundValuePresentationConvertible and CustomPlaygroundPresentationConvertible were rejected as too long of names for the protocol.

···

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

Yes, it will — if a type does not conform to `CustomPlaygroundRepresentable`, PlaygroundLogger will continue to log it structurally.

Connor

···

On Jan 9, 2018, at 3:56 PM, Saagar Jha <saagar@saagarjha.com> wrote:

I’ve just glanced through this, so I apologize if this was already addressed, but will the default behavior (i.e. that of something that doesn’t conform to CustomPlaygroundRepresentable) remain the same?

Saagar Jha

On Jan 9, 2018, at 15:19, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its representation in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate representations for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The new CustomPlaygroundRepresentable protocol would allow instances to return an alternate object or value (as an Any) which would serve as their representation. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundRepresentable protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate representation for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundRepresentable instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

/// A type that supplies a custom representation for playground logging.
///
/// All types have a default representation for playgrounds. This protocol
/// allows types to provide custom representations which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default representation is preferable.
///
/// Playground logging can generate, at a minimum, a structured representation
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized representation of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// representations of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized representations
/// of other types.
///
/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {
  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging. (By returning an Any?, it also allows instances to opt-in to their standard playground representation if that is preferable some cases.)

Implementations of CustomPlaygroundRepresentable may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "MyStruct representation"
  }
}

extension MyOtherStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct representation" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundRepresentable. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate toCustomPlaygroundRepresentable. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundRepresentable if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundRepresentable to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground representations. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide representations as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred representation. (That is, what would PlaygroundLogger select as the representation of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygroundrepresentable-in-the-standard-library>Implement CustomPlaygroundRepresentable in the standard library

As an alternative to implementing CustomPlaygroundRepresentable in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundRepresentable in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundRepresentable protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygroundrepresentable-return-something-other-than-any>Have CustomPlaygroundRepresentable return something other than Any?

One minor alternative considered was to have CustomPlaygroundRepresentable return a value with a more specific type than Any?. For example:

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: CustomPlaygroundRepresentable? { get }
}
or:

protocol PlaygroundRepresentation {}

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: PlaygroundRepresentation? { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundRepresentation.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Something like this was attempted (and reverted) for Swift 3:

<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160808/026088.html>

<https://github.com/apple/swift-evolution/commit/e2610e3fa91b437e06e768aaef6820d755489717>

<https://github.com/apple/swift-xcode-playground-support/commit/0f42ade5a6302cf953a3ed32a892f23e8e150c62>

Is it now possible to `import PlaygroundSupport` outside of a playground?

No, it is not. This is a known limitation of the proposal that is acknowledged in the full text of the proposal.

For the corelibs case (and for libraries provided by the OS), it’s expected that the PlaygroundLogger library be updated to support generating rich log data for new types.

For third-party modules there’s not a great solution, though since playgrounds have fairly limited access to third-party modules and because usage of CustomPlaygroundQuickLookable outside of playgrounds is low (as determined by searching GitHub and the source compatibility suite), the proposal asserts that this is a reasonable trade-off for removing something this domain-specific from the standard library.

Since the new protocol doesn’t itself use any custom types, a workaround for this limitation is possible:

  // MyFramework.swift
  public struct MyStruct {
    var playgroundRepresentation: Any? { … }
  }

  // PlaygroundAuxiliarySource.swift
  import MyFramework
  import PlaygroundSupport

  extension MyStruct: CustomPlaygroundRepresentable {}

The proposal intentionally chose to place this in PlaygroundSupport instead of the standard library because it should be possible in a future Swift release to move this protocol from PlaygroundSupport to the standard library (because PlaygroundSupport doesn’t have any ABI concerns at this time), whereas it wouldn’t be possible to go in the reverse direction. (And if we determine that this protocol should live in the standard library during the course of the review process, I’d still want to proceed otherwise as planned — add the new protocol, deprecate-then-remove the old protocol/enum, and provide a shim library for playgrounds only.)

Connor

···

On Jan 9, 2018, at 4:21 PM, Ben Rimmington <me@benrimmington.com> wrote:

-- Ben

On 9 Jan 2018, at 23:19, Connor Wakamo wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

Saagar Jha

Good afternoon,

Hi Connor,

Huge +1 for this proposal, I’m thrilled you’re cleaning this up. Couple of detail questions:

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>
Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

What is the use-case for a type conforming to this protocol but returning nil? If there is a use case for that, why not have such an implementation return “self” instead?

In short, can we change playgroundRepresentation to return Any instead of Any?. Among other things, doing so could ease the case of playground formatting Optional itself, which should presumably get a conditional conformance to this. :slight_smile:

I believe the rationale behind this was to provide a way to “opt-out” of a customized representation, although now that I think about it, what exactly does the default mean? In particular, what happens in this case?

// I’m not sure how this will be implemented: possibly UIView won’t conform to CustomPlaygroundRepresentable and the first class inheriting from it will do this?
// Either way, it shouldn’t really affect my example since this will just mean that FooView will implement it instead
class UIView: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return self // I assume this is done somewhere in the bowels of PlaygroundSupport or whatever
  }
}

class FooView: UIView {
  override var playgroundRepresentation: Any? {
    return “foo”
  }
}

class BarView: FooView {
  override var playgroundRepresentation: Any? {
    return nil
  }
}

In this case, what’s the default? UIView’s implementation, or that of the immediate parent (FooView’s)?

···

On Jan 9, 2018, at 22:02, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:
On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {

On the naming bikeshed, the closest analog to this feature is CustomStringConvertible, which is used when a type wants to customize the default conversion to string. As such, have you considered CustomPlaygroundConvertible for consistency with it?

The only prior art for the word “Representable” in the standard library is RawRepresentable, which is quite a different concept.

  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }

Again to align with CustomStringConvertible which has a ‘description’ member, it might make sense to name this member “playgroundDescription”.

Thank you again for pushing this forward, this will be much cleaner!

-Chris

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

What is the use-case for a type conforming to this protocol but returning nil? If there is a use case for that, why not have such an implementation return “self” instead?

I assumed it could be used if a type’s playground representation was variable. For instance:

enum MyUnion
{
    case string(String)
    case image(UIImage)
    case none
}

In its implementation of CustomPlaygroundRepresentable, it could return a string if case string, an image if case image, or return nil if case none to just do whatever is the default for enum values.

Admittedly the above is a very contrived example, but I do think it is important to allow types to opt-out.

···

On Jan 9, 2018, at 10:02 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 9, 2018, at 10:02 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:
Good afternoon,

Hi Connor,

Huge +1 for this proposal, I’m thrilled you’re cleaning this up. Couple of detail questions:

Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

What is the use-case for a type conforming to this protocol but returning nil? If there is a use case for that, why not have such an implementation return “self” instead?

In short, can we change playgroundRepresentation to return Any instead of Any?. Among other things, doing so could ease the case of playground formatting Optional itself, which should presumably get a conditional conformance to this. :-)

/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {

On the naming bikeshed, the closest analog to this feature is CustomStringConvertible, which is used when a type wants to customize the default conversion to string. As such, have you considered CustomPlaygroundConvertible for consistency with it?

The only prior art for the word “Representable” in the standard library is RawRepresentable, which is quite a different concept.

  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }

Again to align with CustomStringConvertible which has a ‘description’ member, it might make sense to name this member “playgroundDescription”.

Thank you again for pushing this forward, this will be much cleaner!

-Chris

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

Good afternoon,

Hi Connor,

Huge +1 for this proposal, I’m thrilled you’re cleaning this up. Couple of detail questions:

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>
Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

What is the use-case for a type conforming to this protocol but returning nil? If there is a use case for that, why not have such an implementation return “self” instead?

Riley and Saagar answered this down-thread, but to confirm — returning nil would allow some instances of a type to use the “default” playground logging presentation while others use an alternate presentation instead.

This isn’t handled by `return self` because, unless I’m mistaken, there’s no way to detect that from the caller’s side (e.g. with two `Any` values, I can’t do `self === self.playgroundRepresentation`). This would be necessary because the intention is that `CustomPlaygroundRepresentable` conformances can chain — if I return an object/value which itself conforms to `CustomPlaygroundRepresentable`, then the playground logger should follow that so that I’m presented the same way as whatever I return would have been. (That’s probably not absolutely true, as the PlaygroundLogger library will likely have some sort of failsafe to prevent infinite chaining here. But I wouldn’t want to rely on such a failsafe mechanism in the design of this API.)

In short, can we change playgroundRepresentation to return Any instead of Any?. Among other things, doing so could ease the case of playground formatting Optional itself, which should presumably get a conditional conformance to this. :slight_smile:

I don’t think we can change this to return `Any` instead of `Any?`. I think there are potentially cases where a developer might want to selectively opt-in to this behavior.

I also don’t think that `Optional` would get a conditional conformance to this. I’m not proposing that any standard library or corelibs types gain conformances to this protocol. Instead, it’s up to a playground logger (such as PlaygroundLogger in swift-xcode-playground-support <https://github.com/apple/swift-xcode-playground-support>) to recognize these types and handle them accordingly. The playground logger would look through the `Optional` so that this would effectively be true, but ideally the log data generated by a logger would indicate that it was wrapped by `Optional.some`.

One possibility would be to change the API so that it returns an enum. Imagine:

  enum PlaygroundLoggingBehavior {
    /// Asks the playground logger to generate the standard logging for `self`.
    case standard

    /// Asks the playground logger to generate logging for the given `Any` instead of `self`.
    case custom(Any)
  }

  protocol CustomPlaygroundLoggable {
    /// Returns the `PlaygroundLoggingBehavior` to use for `self`.
    var playgroundLoggingBehavior: PlaygroundLoggingBehavior { get }
  }

(To Saagar’s point in another email — you could even add a `case none` to PlaygroundLoggingBehavior to inhibit logging of a particular instance.)

`CustomPlaygroundLoggable` would be a little clunkier to implement than `CustomPlaygroundRepresentable` is, as in the common case folks would have to write `return .custom(…)`. It’s possible that the clarity and additional flexibility this grants outweighs that cost; I’m not sure, and would love feedback on that.

/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {

On the naming bikeshed, the closest analog to this feature is CustomStringConvertible, which is used when a type wants to customize the default conversion to string. As such, have you considered CustomPlaygroundConvertible for consistency with it?

The only prior art for the word “Representable” in the standard library is RawRepresentable, which is quite a different concept.

  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }

Again to align with CustomStringConvertible which has a ‘description’ member, it might make sense to name this member “playgroundDescription”.

I’m definitely open to different names for this. (`CustomPlaygroundRepresentable` was inspired by the API I’m removing, `CustomPlaygroundQuickLookable`, as they both take their sole property and make them -able.)

I do like the `playgroundDescription` name for the property, but am a little hesitant to use the name `CustomPlaygroundConvertible` because conforming types can’t be converted to playgrounds. I can’t come up with an appropriate word in `CustomPlaygroundThingConvertible` to use in place of `Thing`, though. (If we end up pivoting to the enum I described above then something like `CustomPlaygroundLoggable` would be more appropriate.)

Connor

···

On Jan 9, 2018, at 10:02 PM, Chris Lattner <clattner@nondot.org> wrote:
On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

To that end, I am proposing the following:

- Introduce a new protocol, CustomPlaygroundRepresentable, in the
PlaygroundSupport library in Swift 4.1:

protocol CustomPlaygroundRepresentable {
/// Returns an alternate object or value which should stand in for the
receiver in playground logging, or nil if the receiver’s default
representation is preferred.
var playgroundRepresentation: Any? { get }
}

I was always surprised that "Playground" was a word/concept that the
Standard Library used, as playgrounds are an Xcode feature and iOS app
which are not part of the Swift open source project.

Agreed — which is why this proposal moves it into the playground-specific PlaygroundSupport library instead of leaving it in the standard library.

I think it might be better if names for these could be found which
reflect the functionality they provide, in a non "playground" specific
way. After all, AFAIK it would be possible for non-"playground" uses
of CustomPlaygroundRepresentable.

As mentioned in another email, I’m open to naming suggestions. That being said, in the same way that there’s `CustomStringConvertible` and `CustomDebugStringConvertible`, if we come up with a generic name for this, I think I’d still probably want a playground-specific version of it that lives in the PlaygroundSupport library so that code can customize its behavior to be appropriate for different environments/audiences.

Connor

···

On Jan 10, 2018, at 1:58 AM, Ian Partridge <ian@poncho.org.uk> wrote:
On 9 January 2018 at 23:19, Connor Wakamo via swift-evolution > <swift-evolution@swift.org> wrote:

Perhaps bring the three special case literals into this as well? (File, image, color)

-- E

···

On Jan 9, 2018, at 4:53 PM, Ben Cohen via swift-evolution <swift-evolution@swift.org> wrote:

Big +1 to this. Getting these types out of the standard library and into a more suitable domain-specific framework, prior to declaring ABI Stability, will give us flexibility to evolve them appropriately over time.

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its representation in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate representations for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The new CustomPlaygroundRepresentable protocol would allow instances to return an alternate object or value (as an Any) which would serve as their representation. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundRepresentable protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate representation for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundRepresentable instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

/// A type that supplies a custom representation for playground logging.
///
/// All types have a default representation for playgrounds. This protocol
/// allows types to provide custom representations which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default representation is preferable.
///
/// Playground logging can generate, at a minimum, a structured representation
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized representation of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// representations of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized representations
/// of other types.
///
/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {
  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging. (By returning an Any?, it also allows instances to opt-in to their standard playground representation if that is preferable some cases.)

Implementations of CustomPlaygroundRepresentable may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return "MyStruct representation"
  }
}

extension MyOtherStruct: CustomPlaygroundRepresentable {
  var playgroundRepresentation: Any? {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct representation" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundRepresentable. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate toCustomPlaygroundRepresentable. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundRepresentable if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundRepresentable to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground representations. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide representations as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred representation. (That is, what would PlaygroundLogger select as the representation of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygroundrepresentable-in-the-standard-library>Implement CustomPlaygroundRepresentable in the standard library

As an alternative to implementing CustomPlaygroundRepresentable in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundRepresentable in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundRepresentable protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygroundrepresentable-return-something-other-than-any>Have CustomPlaygroundRepresentable return something other than Any?

One minor alternative considered was to have CustomPlaygroundRepresentable return a value with a more specific type than Any?. For example:

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: CustomPlaygroundRepresentable? { get }
}
or:

protocol PlaygroundRepresentation {}

protocol CustomPlaygroundRepresentable {
  var playgroundRepresentation: PlaygroundRepresentation? { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundRepresentation.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)
_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution

Overall, this looks pretty good. Just one nitpick: you seem to have forgotten to take out references to default behavior. I’ve put comments inline wherever I spotted them.

Saagar Jha

Thanks to everyone who’s commented on this proposal. I’ve updated it with feedback from this round of discussion, and I plan to submit a PR to the swift-evolution repo with this proposal to start the official review process in the next couple of days (as I finish up the implementation of this proposal).

Since this mailing list will be offline later today for the transition to Discourse, please let me know directly if you have any feedback before the transition is complete and I’ll take it into account.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its description in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate descriptions for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The newCustomPlaygroundDisplayConvertible protocol would allow instances to return an alternate object or value (as an Any) which would serve as their description. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20180108/042639.html>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundDisplayConvertible protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate description for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundDisplayConvertible instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

It looks like you’ve forgotten to remove nil here.

/// A type that supplies a custom description for playground logging.
///
/// All types have a default description for playgrounds. This protocol
/// allows types to provide custom descriptions which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default description is preferable.

Here as well.

···

On Jan 17, 2018, at 15:54, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:

///
/// Playground logging can generate, at a minimum, a structured description
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized description of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// descriptions of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized descriptions
/// of other types.
///
/// Implementors of `CustomPlaygroundDisplayConvertible` may return a value of
/// one of the above types to also receive a specialized log description.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundDisplayConvertible {
  /// Returns the custom playground description for this instance.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundDescription: Any { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging.

Implementations of CustomPlaygroundDisplayConvertible may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "MyStruct description for playgrounds"
  }
}

extension MyOtherStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct description for playgrounds" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundDisplayConvertible. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate to CustomPlaygroundDisplayConvertible. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundDisplayConvertible if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundDisplayConvertible to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground descriptions. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide descriptions as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred description. (That is, what would PlaygroundLogger select as the description of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygrounddisplayconvertible-in-the-standard-library>Implement CustomPlaygroundDisplayConvertible in the standard library

As an alternative to implementing CustomPlaygroundDisplayConvertible in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundDisplayConvertible in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundDisplayConvertible protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-something-other-than-any>Have CustomPlaygroundDisplayConvertible return something other than Any

One minor alternative considered was to have CustomPlaygroundDisplayConvertible return a value with a more specific type than Any. For example:

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: CustomPlaygroundDisplayConvertible { get }
}
or:

protocol PlaygroundDescription {}

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: PlaygroundDescription { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundDescription.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-an-any-instead-of-an-any>Have CustomPlaygroundDisplayConvertible return an Any? instead of an Any

One alternative considered was to have CustomPlaygroundDisplayConvertible return an Any? instead of an Any. This would permit individual instances to opt-out of a custom playground description by returning nil instead of a concrete value or object.

Although that capability is no longer present, in most cases implementors of CustomPlaygroundDisplayConvertiblemay return a custom description which closely mirrors their default description. One big exception to this are classes which are considered core types, such as NSView and UIView, as one level of subclass may wish to customize its description while deeper level may wish to use the default description (which is currently a rendered image of the view). This proposal does not permit that; the second-level subclass must return a custom description one way or another, and due to the chaining nature of CustomPlaygroundDisplayConvertible implementations, it cannot return self and have that reliably indicate to the playground logger implementation that that means "don't use a custom description".

This issue seems to be limited enough that it should not tarnish the API design as a whole. Returning Any and not Any?is easier to understand, so this proposal opts to do that. Should this be a larger issue than anticipated, a future proposal could introduce a struct like DefaultPlaygroundDescription<T> which the playground logger would understand to mean "don't check for a CustomPlaygroundDisplayConvertible conformance on the wrapped value".

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternate-names-for-customplaygrounddisplayconvertible>Alternate Names for CustomPlaygroundDisplayConvertible

Finally, as this introduces a new protocol, there are other possible names:

CustomPlaygroundRepresentable
CustomPlaygroundConvertible
CustomPlaygroundPreviewConvertible
CustomPlaygroundQuickLookConvertible
CustomPlaygroundValuePresentationConvertible
CustomPlaygroundPresentationConvertible
CustomPlaygroundRepresentable was rejected as it does not match the naming convention established byCustomStringConvertible/CustomDebugStringConvertible. CustomPlaygroundConvertible was rejected as not being specific enough -- types conforming to this protocol are not themselves convertible to playgrounds, but are instead custom convertible for playground display. CustomPlaygroundPreviewConvertible is very similar toCustomPlaygroundDisplayConvertible, but implies more about the presentation than is appropriate as a playground environment is free to display it any way it wants, not just as a "preview". CustomPlaygroundQuickLookConvertible was rejected as it potentially invokes the to-be-removed PlaygroundQuickLook enum.CustomPlaygroundValuePresentationConvertible and CustomPlaygroundPresentationConvertible were rejected as too long of names for the protocol.

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

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

LGTM

-Chris

···

On Jan 17, 2018, at 3:54 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org> wrote:

Thanks to everyone who’s commented on this proposal. I’ve updated it with feedback from this round of discussion, and I plan to submit a PR to the swift-evolution repo with this proposal to start the official review process in the next couple of days (as I finish up the implementation of this proposal).

Since this mailing list will be offline later today for the transition to Discourse, please let me know directly if you have any feedback before the transition is complete and I’ll take it into account.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its description in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate descriptions for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The newCustomPlaygroundDisplayConvertible protocol would allow instances to return an alternate object or value (as an Any) which would serve as their description. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20180108/042639.html>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundDisplayConvertible protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate description for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundDisplayConvertible instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

/// A type that supplies a custom description for playground logging.
///
/// All types have a default description for playgrounds. This protocol
/// allows types to provide custom descriptions which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default description is preferable.
///
/// Playground logging can generate, at a minimum, a structured description
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized description of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// descriptions of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized descriptions
/// of other types.
///
/// Implementors of `CustomPlaygroundDisplayConvertible` may return a value of
/// one of the above types to also receive a specialized log description.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundDisplayConvertible {
  /// Returns the custom playground description for this instance.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundDescription: Any { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging.

Implementations of CustomPlaygroundDisplayConvertible may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "MyStruct description for playgrounds"
  }
}

extension MyOtherStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct description for playgrounds" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundDisplayConvertible. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate to CustomPlaygroundDisplayConvertible. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundDisplayConvertible if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundDisplayConvertible to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground descriptions. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide descriptions as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred description. (That is, what would PlaygroundLogger select as the description of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygrounddisplayconvertible-in-the-standard-library>Implement CustomPlaygroundDisplayConvertible in the standard library

As an alternative to implementing CustomPlaygroundDisplayConvertible in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundDisplayConvertible in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundDisplayConvertible protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-something-other-than-any>Have CustomPlaygroundDisplayConvertible return something other than Any

One minor alternative considered was to have CustomPlaygroundDisplayConvertible return a value with a more specific type than Any. For example:

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: CustomPlaygroundDisplayConvertible { get }
}
or:

protocol PlaygroundDescription {}

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: PlaygroundDescription { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundDescription.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-an-any-instead-of-an-any>Have CustomPlaygroundDisplayConvertible return an Any? instead of an Any

One alternative considered was to have CustomPlaygroundDisplayConvertible return an Any? instead of an Any. This would permit individual instances to opt-out of a custom playground description by returning nil instead of a concrete value or object.

Although that capability is no longer present, in most cases implementors of CustomPlaygroundDisplayConvertiblemay return a custom description which closely mirrors their default description. One big exception to this are classes which are considered core types, such as NSView and UIView, as one level of subclass may wish to customize its description while deeper level may wish to use the default description (which is currently a rendered image of the view). This proposal does not permit that; the second-level subclass must return a custom description one way or another, and due to the chaining nature of CustomPlaygroundDisplayConvertible implementations, it cannot return self and have that reliably indicate to the playground logger implementation that that means "don't use a custom description".

This issue seems to be limited enough that it should not tarnish the API design as a whole. Returning Any and not Any?is easier to understand, so this proposal opts to do that. Should this be a larger issue than anticipated, a future proposal could introduce a struct like DefaultPlaygroundDescription<T> which the playground logger would understand to mean "don't check for a CustomPlaygroundDisplayConvertible conformance on the wrapped value".

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternate-names-for-customplaygrounddisplayconvertible>Alternate Names for CustomPlaygroundDisplayConvertible

Finally, as this introduces a new protocol, there are other possible names:

CustomPlaygroundRepresentable
CustomPlaygroundConvertible
CustomPlaygroundPreviewConvertible
CustomPlaygroundQuickLookConvertible
CustomPlaygroundValuePresentationConvertible
CustomPlaygroundPresentationConvertible
CustomPlaygroundRepresentable was rejected as it does not match the naming convention established byCustomStringConvertible/CustomDebugStringConvertible. CustomPlaygroundConvertible was rejected as not being specific enough -- types conforming to this protocol are not themselves convertible to playgrounds, but are instead custom convertible for playground display. CustomPlaygroundPreviewConvertible is very similar toCustomPlaygroundDisplayConvertible, but implies more about the presentation than is appropriate as a playground environment is free to display it any way it wants, not just as a "preview". CustomPlaygroundQuickLookConvertible was rejected as it potentially invokes the to-be-removed PlaygroundQuickLook enum.CustomPlaygroundValuePresentationConvertible and CustomPlaygroundPresentationConvertible were rejected as too long of names for the protocol.

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

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

If you want a type to display as nothing (e.g. to avoid the perf impact of formatting it) just make the playground representation be an empty string or something.

-Chris

···

On Jan 9, 2018, at 11:30 PM, Saagar Jha <saagar@saagarjha.com> wrote:

In short, can we change playgroundRepresentation to return Any instead of Any?. Among other things, doing so could ease the case of playground formatting Optional itself, which should presumably get a conditional conformance to this. :slight_smile:

I believe the rationale behind this was to provide a way to “opt-out” of a customized representation, although now that I think about it, what exactly does the default mean? In particular, what happens in this case?

You should just be able to return ‘self' in that case?

-Chris

···

On Jan 10, 2018, at 10:10 AM, Riley Testut <rileytestut@gmail.com> wrote:

On Jan 9, 2018, at 10:02 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

What is the use-case for a type conforming to this protocol but returning nil? If there is a use case for that, why not have such an implementation return “self” instead?

I assumed it could be used if a type’s playground representation was variable. For instance:

enum MyUnion
{
    case string(String)
    case image(UIImage)
    case none
}

In its implementation of CustomPlaygroundRepresentable, it could return a string if case string, an image if case image, or return nil if case none to just do whatever is the default for enum values.

Admittedly the above is a very contrived example, but I do think it is important to allow types to opt-out.

What is the use-case for a type conforming to this protocol but returning nil? If there is a use case for that, why not have such an implementation return “self” instead?

Riley and Saagar answered this down-thread, but to confirm — returning nil would allow some instances of a type to use the “default” playground logging presentation while others use an alternate presentation instead.

Right, this gets back to the question: what is the use case for this? When would a type want to “sometimes” replace the default representation?

It seems to me that a type author either wants to take control of presentation or not. While I’m sure we could imagine some use case for the behavior you’re describing, is it big enough to make it worth complicating the API?

This isn’t handled by `return self` because, unless I’m mistaken, there’s no way to detect that from the caller’s side (e.g. with two `Any` values, I can’t do `self === self.playgroundRepresentation`).

Ok

In short, can we change playgroundRepresentation to return Any instead of Any?. Among other things, doing so could ease the case of playground formatting Optional itself, which should presumably get a conditional conformance to this. :slight_smile:

I don’t think we can change this to return `Any` instead of `Any?`. I think there are potentially cases where a developer might want to selectively opt-in to this behavior.

Which cases? How important are they?

I also don’t think that `Optional` would get a conditional conformance to this. I’m not proposing that any standard library or corelibs types gain conformances to this protocol. Instead, it’s up to a playground logger (such as PlaygroundLogger in swift-xcode-playground-support <https://github.com/apple/swift-xcode-playground-support>) to recognize these types and handle them accordingly. The playground logger would look through the `Optional` so that this would effectively be true, but ideally the log data generated by a logger would indicate that it was wrapped by `Optional.some`.

Why not? I understand that that is how the old algorithm worked, but it contained a lot of special case hacks due to the state of Swift 1 :-). This is a chance to dissolve those away.

To be clear, I would expect that the conformance for optional would be defined in the playground module along with this protocol - it wouldn’t be defined in the standard library itself.

One possibility would be to change the API so that it returns an enum. Imagine:

  enum PlaygroundLoggingBehavior {
    /// Asks the playground logger to generate the standard logging for `self`.
    case standard

    /// Asks the playground logger to generate logging for the given `Any` instead of `self`.
    case custom(Any)
  }

  protocol CustomPlaygroundLoggable {
    /// Returns the `PlaygroundLoggingBehavior` to use for `self`.
    var playgroundLoggingBehavior: PlaygroundLoggingBehavior { get }
  }

(To Saagar’s point in another email — you could even add a `case none` to PlaygroundLoggingBehavior to inhibit logging of a particular instance.)

`CustomPlaygroundLoggable` would be a little clunkier to implement than `CustomPlaygroundRepresentable` is, as in the common case folks would have to write `return .custom(…)`. It’s possible that the clarity and additional flexibility this grants outweighs that cost; I’m not sure, and would love feedback on that.

I just don’t understand the usecase for “conditional customizing” at all. By way of example, we don’t have the ability to do that with CustomStringConvertible. What is different about this case?

/// Implementors of `CustomPlaygroundRepresentable` may return a value of one of
/// the above types to also receive a specialized log representation.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundRepresentable {

On the naming bikeshed, the closest analog to this feature is CustomStringConvertible, which is used when a type wants to customize the default conversion to string. As such, have you considered CustomPlaygroundConvertible for consistency with it?

The only prior art for the word “Representable” in the standard library is RawRepresentable, which is quite a different concept.

  /// Returns the custom playground representation for this instance, or nil if
  /// the default representation should be used.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundRepresentation: Any? { get }

Again to align with CustomStringConvertible which has a ‘description’ member, it might make sense to name this member “playgroundDescription”.

I’m definitely open to different names for this. (`CustomPlaygroundRepresentable` was inspired by the API I’m removing, `CustomPlaygroundQuickLookable`, as they both take their sole property and make them -able.)

I do like the `playgroundDescription` name for the property, but am a little hesitant to use the name `CustomPlaygroundConvertible` because conforming types can’t be converted to playgrounds. I can’t come up with an appropriate word in `CustomPlaygroundThingConvertible` to use in place of `Thing`, though. (If we end up pivoting to the enum I described above then something like `CustomPlaygroundLoggable` would be more appropriate.)

I would strongly recommend aligning with the state of the art in CustomStringConvertible (which has been extensively discussed) and ignore the precedent in the existing playground logging stuff (which hasn’t).

-Chris

···

On Jan 10, 2018, at 2:10 PM, Connor Wakamo <cwakamo@apple.com> wrote:

Great, thank you! I’ve updated the proposal to remove these references.

Connor

···

On Jan 17, 2018, at 4:26 PM, Saagar Jha <saagar@saagarjha.com> wrote:

Overall, this looks pretty good. Just one nitpick: you seem to have forgotten to take out references to default behavior. I’ve put comments inline wherever I spotted them.

Saagar Jha

On Jan 17, 2018, at 15:54, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks to everyone who’s commented on this proposal. I’ve updated it with feedback from this round of discussion, and I plan to submit a PR to the swift-evolution repo with this proposal to start the official review process in the next couple of days (as I finish up the implementation of this proposal).

Since this mailing list will be offline later today for the transition to Discourse, please let me know directly if you have any feedback before the transition is complete and I’ll take it into account.

Thanks,
Connor

Playground QuickLook API Revamp

Proposal: SE-NNNN <https://github.com/cwakamo/swift-evolution/blob/playground-quicklook-api-revamp/proposals/NNNN-playground-quicklook-api-revamp.md>
Authors: Connor Wakamo <https://github.com/cwakamo>
Review Manager: TBD
Status: Awaiting implementation
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#introduction>Introduction

The standard library currently includes API which allows a type to customize its description in Xcode playgrounds and Swift Playgrounds. This API takes the form of the PlaygroundQuickLook enum which enumerates types which are supported for quick looks, and the CustomPlaygroundQuickLookable protocol which allows a type to return a custom PlaygroundQuickLook value for an instance.

This is brittle, and to avoid dependency inversions, many of the cases are typed as taking Any instead of a more appropriate type. This proposal suggests that we deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 so they can be removed entirely in Swift 5, preventing them from being included in the standard library's stable ABI. To maintain compatibility with older playgrounds, the deprecated symbols will be present in a temporary compatibility shim library which will be automatically imported in playground contexts. (This will represent an intentional source break for projects, packages, and other non-playground Swift code which use PlaygroundQuickLook or CustomPlaygroundQuickLookable when they switch to the Swift 5.0 compiler, even in the compatibility modes.)

Since it is still useful to allow types to provide alternate descriptions for playgrounds, we propose to add a new protocol to the PlaygroundSupport framework which allows types to do just that. (PlaygroundSupport is a framework delivered by the swift-xcode-playground-support project <https://github.com/apple/swift-xcode-playground-support> which provides API specific to working in the playgrounds environment). The newCustomPlaygroundDisplayConvertible protocol would allow instances to return an alternate object or value (as an Any) which would serve as their description. The PlaygroundLogger framework, also part of swift-xcode-playground-support, will be updated to understand this protocol.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20180108/042639.html>
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#motivation>Motivation

The PlaygroundQuickLook enum which currently exists in the standard library is substandard:

public enum PlaygroundQuickLook {
  case text(String)
  case int(Int64)
  case uInt(UInt64)
  case float(Float32)
  case double(Float64)
  case image(Any)
  case sound(Any)
  case color(Any)
  case bezierPath(Any)
  case attributedString(Any)
  case rectangle(Float64, Float64, Float64, Float64)
  case point(Float64, Float64)
  case size(Float64, Float64)
  case bool(Bool)
  case range(Int64, Int64)
  case view(Any)
  case sprite(Any)
  case url(String)
  case _raw([UInt8], String)
}
The names of these enum cases do not necessarily match current Swift naming conventions (e.g. uInt), and many cases are typed as Any to avoid dependency inversions between the standard library and higher-level frameworks like Foundation and AppKit or UIKit. It also contains cases which the PlaygroundLogger framework does not understand (e.g. sound), and this listing of cases introduces revlock between PlaygroundLogger and the standard library that makes it challenging to introduce support for new types of quick looks.

Values of this enum are provided to the PlaygroundLogger framework by types via conformances to the CustomPlaygroundQuickLookable protocol:

public protocol CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook { get }
}
This protocol itself is not problematic, but if PlaygroundQuickLook is being removed, then it needs to be removed as well. Additionally, there is a companion underscored protocol which should be removed as well:

public protocol _DefaultCustomPlaygroundQuickLookable {
  var _defaultCustomPlaygroundQuickLook: PlaygroundQuickLook { get }
}
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#proposed-solution>Proposed solution

To solve this issue, we propose the following changes:

Introduce a new CustomPlaygroundDisplayConvertible protocol in PlaygroundSupport in Swift 4.1 to allow types to provide an alternate description for playground logging
Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1, suggesting users use CustomPlaygroundDisplayConvertible instead
Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable from the standard library in Swift 5.0
Provide an automatically-imported shim library for the playgrounds context to provide the deprecated instances of PlaygroundQuickLook and CustomPlaygroundQuickLookable for pre-Swift 5 playgrounds
<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#detailed-design>Detailed design

To provide a more flexible API, we propose deprecating and ultimately removing the PlaygroundQuickLook enum and CustomPlaygroundQuickLookable protocol in favor of a simpler design. Instead, we propose introducing a protocol which just provides the ability to return an Any (or nil) that serves as a stand-in for the instance being logged:

It looks like you’ve forgotten to remove nil here.

/// A type that supplies a custom description for playground logging.
///
/// All types have a default description for playgrounds. This protocol
/// allows types to provide custom descriptions which are then logged in
/// place of the original instance. Alternatively, implementors may choose to
/// return `nil` in instances where the default description is preferable.

Here as well.

///
/// Playground logging can generate, at a minimum, a structured description
/// of any type. Playground logging is also capable of generating a richer,
/// more specialized description of core types -- for instance, the contents
/// of a `String` are logged, as are the components of an `NSColor` or
/// `UIColor`.
///
/// The current playground logging implementation logs specialized
/// descriptions of at least the following types:
///
/// - `String` and `NSString`
/// - `Int` and `UInt` (including the sized variants)
/// - `Float` and `Double`
/// - `Bool`
/// - `Date` and `NSDate`
/// - `NSAttributedString`
/// - `NSNumber`
/// - `NSRange`
/// - `URL` and `NSURL`
/// - `CGPoint`, `CGSize`, and `CGRect`
/// - `NSColor`, `UIColor`, `CGColor`, and `CIColor`
/// - `NSImage`, `UIImage`, `CGImage`, and `CIImage`
/// - `NSBezierPath` and `UIBezierPath`
/// - `NSView` and `UIView`
///
/// Playground logging may also be able to support specialized descriptions
/// of other types.
///
/// Implementors of `CustomPlaygroundDisplayConvertible` may return a value of
/// one of the above types to also receive a specialized log description.
/// Implementors may also return any other type, and playground logging will
/// generated structured logging for the returned value.
public protocol CustomPlaygroundDisplayConvertible {
  /// Returns the custom playground description for this instance.
  ///
  /// If this type has value semantics, the instance returned should be
  /// unaffected by subsequent mutations if possible.
  var playgroundDescription: Any { get }
}
Additionally, instead of placing this protocol in the standard library, we propose placing this protocol in the PlaygroundSupport framework, as it is only of interest in the playgrounds environment. Should demand warrant it, a future proposal could suggest lowering this protocol into the standard library.

If this proposal is accepted, then code like the following:

extension MyStruct: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("A description of this MyStruct instance")
  }
}
would be replaced with something like the following:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "A description of this MyStruct instance"
  }
}
This proposal also allows types which wish to be represented structurally (like an array or dictionary) to return a type which is logged structurally instead of requiring an implementation of the CustomReflectable protocol:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return [1, 2, 3]
  }
}
This is an enhancement over the existing CustomPlaygroundQuickLookable protocol, which only supported returning opaque, quick lookable values for playground logging.

Implementations of CustomPlaygroundDisplayConvertible may potentially chain from one to another. For instance, with:

extension MyStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "MyStruct description for playgrounds"
  }
}

extension MyOtherStruct: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return MyStruct()
  }
}
Playground logging for MyOtherStruct would generate the string "MyStruct description for playgrounds" rather than the structural view of MyStruct. It is legal, however, for playground logging implementations to cap chaining to a reasonable limit to guard against infinite recursion.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#source-compatibility>Source compatibility

This proposal is explicitly suggesting that we make a source-breaking change in Swift 5 to remove PlaygroundQuickLook, CustomPlaygroundQuickLookable, and _DefaultCustomPlaygroundQuickLookable. Looking at a GitHub search, there are fewer than 900 references to CustomPlaygroundQuickLookable in Swift source code; from a cursory glance, many of these are duplicates, from forks of the Swift repo itself (i.e. the definition of CustomPlaygroundQuickLookable in the standard library), or are clearly implemented using pre-Swift 3 names of the enum cases in PlaygroundQuickLook. (As a point of comparison, there are over 185,000 references to CustomStringConvertible in Swift code on GitHub, and over 145,000 references to CustomDebugStringConvertible, so CustomPlaygroundQuickLookable is clearly used many orders of magnitude less than those protocols.) Furthermore, it does not appear that any projects currently in the source compatibility suite use these types.

However, to mitigate the impact of this change, we propose to provide a limited source compatibility shim for the playgrounds context. This will be delivered as part of the swift-xcode-playground-support project as a library containing the deprecated PlaygroundQuickLook and CustomPlaygroundQuickLookable protocols. This library would be imported automatically in playgrounds. This source compatibility shim would not be available outside of playgrounds, so any projects, packages, or other Swift code would be intentionally broken by this change when upgrading to the Swift 5.0 compiler, even when compiling in a compatibility mode.

Due to the limited usage of these protocols, and the potential challenge in migration, this proposal does not include any proposed migrator changes to support the replacement of CustomPlaygroundQuickLookable withCustomPlaygroundDisplayConvertible. Instead, we intend for Swift 4.1 to be a deprecation period for these APIs, allowing any code bases which implement CustomPlaygroundQuickLookable to manually switch to the new protocol. While this migration may not be trivial programatically, it should -- in most cases -- be fairly trivial for someone to hand-migrate to CustomPlaygroundDisplayConvertible. During the deprecation period, the PlaygroundLogger framework will continue to honor implementations of CustomPlaygroundQuickLookable, though it will prefer implementations ofCustomPlaygroundDisplayConvertible if both are present on a given type.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-abi-stability>Effect on ABI stability

This proposal affects ABI stability as it removes an enum and a pair of protocols from the standard library. Since this proposal proposes adding CustomPlaygroundDisplayConvertible to PlaygroundSupport instead of the standard library, there is no impact of ABI stability from the new protocol, as PlaygroundSupport does not need to maintain a stable ABI, as its clients -- playgrounds -- are always recompiled from source.

Since playgrounds are always compiled from source, the temporary shim library does not represent a new ABI guarantee, and it may be removed if the compiler drops support for the Swift 3 and 4 compatibility modes in a future Swift release.

Removing PlaygroundQuickLook from the standard library also potentially allows us to remove a handful of runtime entry points which were included to support the PlaygroundQuickLook(reflecting:) API.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API resilience.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternatives-considered>Alternatives considered

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#do-nothing>Do nothing

One valid alternative to this proposal is to do nothing: we could continue to live with the existing enum and protocol. As noted above, these are fairly poor, and do not serve the needs of playgrounds particularly well. Since this is our last chance to remove them prior to ABI stability, we believe that doing nothing is not an acceptable alternative.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#provide-type-specific-protocols>Provide type-specific protocols

Another alternative we considered was to provide type-specific protocols for providing playground descriptions. We would introduce new protocols like CustomNSColorConvertible, CustomNSAttributedStringConvertible, etc. which would allow types to provide descriptions as each of the opaquely-loggable types supported by PlaygroundLogger.

This alternative was rejected as it would balloon the API surface for playgrounds, and it also would not provide a good way to select a preferred description. (That is, what would PlaygroundLogger select as the description of an instance if it implemented both CustomNSColorConvertible and CustomNSAttributedStringConvertible?)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#implement-customplaygrounddisplayconvertible-in-the-standard-library>Implement CustomPlaygroundDisplayConvertible in the standard library

As an alternative to implementing CustomPlaygroundDisplayConvertible in PlaygroundSupport, we could implement it in the standard library. This would make it available in all contexts (i.e. in projects and packages, not just in playgrounds), but this protocol is not particularly useful outside of the playground context, so this proposal elects not to placeCustomPlaygroundDisplayConvertible in the standard library.

Additionally, it should be a source-compatible change to move this protocol to the standard library in a future Swift version should that be desirable. Since playgrounds are always compiled from source, the fact that this would be an ABI change for PlaygroundSupport does not matter, and a compatibility typealias could be provided in PlaygroundSupport to maintain compatibility with code which explicitly qualified the name of the CustomPlaygroundDisplayConvertible protocol.

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-something-other-than-any>Have CustomPlaygroundDisplayConvertible return something other than Any

One minor alternative considered was to have CustomPlaygroundDisplayConvertible return a value with a more specific type than Any. For example:

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: CustomPlaygroundDisplayConvertible { get }
}
or:

protocol PlaygroundDescription {}

protocol CustomPlaygroundDisplayConvertible {
  var playgroundDescription: PlaygroundDescription { get }
}
In both cases, core types which the playground logger supports would conform to the appropriate protocol such that they could be returned from implementations of playgroundDescription.

The benefit to this approach is that it is more self-documenting than the approach proposed in this document, as a user can look up all of the types which conform to a particular protocol to know what the playground logger understands. However, this approach has a number of pitfalls, largely because it's intentional that the proposal uses Any instead of a more-constrained protocol. It should be possible to return anything as the stand-in for an instance, including values without opaque playground quick look views, so that it's easier to construct an alternate structured view of a type (without having to override the more complex CustomReflectable protocol). Furthermore, by making the API in the library use a general type like Any, this proposal prevents revlock from occurring between IDEs and the libraries, as the IDE's playground logger can implement support for opaque logging of new types without requiring library changes. (And IDEs can opt to support a subset of types if they prefer, whereas if the libraries promised support an IDE would effectively be compelled to provide it.)

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#have-customplaygrounddisplayconvertible-return-an-any-instead-of-an-any>Have CustomPlaygroundDisplayConvertible return an Any? instead of an Any

One alternative considered was to have CustomPlaygroundDisplayConvertible return an Any? instead of an Any. This would permit individual instances to opt-out of a custom playground description by returning nil instead of a concrete value or object.

Although that capability is no longer present, in most cases implementors of CustomPlaygroundDisplayConvertiblemay return a custom description which closely mirrors their default description. One big exception to this are classes which are considered core types, such as NSView and UIView, as one level of subclass may wish to customize its description while deeper level may wish to use the default description (which is currently a rendered image of the view). This proposal does not permit that; the second-level subclass must return a custom description one way or another, and due to the chaining nature of CustomPlaygroundDisplayConvertible implementations, it cannot return self and have that reliably indicate to the playground logger implementation that that means "don't use a custom description".

This issue seems to be limited enough that it should not tarnish the API design as a whole. Returning Any and not Any?is easier to understand, so this proposal opts to do that. Should this be a larger issue than anticipated, a future proposal could introduce a struct like DefaultPlaygroundDescription<T> which the playground logger would understand to mean "don't check for a CustomPlaygroundDisplayConvertible conformance on the wrapped value".

<https://github.com/cwakamo/swift-evolution/tree/playground-quicklook-api-revamp#alternate-names-for-customplaygrounddisplayconvertible>Alternate Names for CustomPlaygroundDisplayConvertible

Finally, as this introduces a new protocol, there are other possible names:

CustomPlaygroundRepresentable
CustomPlaygroundConvertible
CustomPlaygroundPreviewConvertible
CustomPlaygroundQuickLookConvertible
CustomPlaygroundValuePresentationConvertible
CustomPlaygroundPresentationConvertible
CustomPlaygroundRepresentable was rejected as it does not match the naming convention established byCustomStringConvertible/CustomDebugStringConvertible. CustomPlaygroundConvertible was rejected as not being specific enough -- types conforming to this protocol are not themselves convertible to playgrounds, but are instead custom convertible for playground display. CustomPlaygroundPreviewConvertible is very similar toCustomPlaygroundDisplayConvertible, but implies more about the presentation than is appropriate as a playground environment is free to display it any way it wants, not just as a "preview". CustomPlaygroundQuickLookConvertible was rejected as it potentially invokes the to-be-removed PlaygroundQuickLook enum.CustomPlaygroundValuePresentationConvertible and CustomPlaygroundPresentationConvertible were rejected as too long of names for the protocol.

On Jan 9, 2018, at 3:19 PM, Connor Wakamo via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Good afternoon,

In preparation for ABI stability, I’ve reviewed the API exposed by the standard library for providing customized “quick looks” in playgrounds. This is exposed as the PlaygroundQuickLook enum and the CustomPlaygroundQuickLookable protocol. The PlaygroundQuickLook has a handful of issues:

  - It hard-codes the list of supported types in the standard library, meaning that PlaygroundLogger/IDEs cannot gain support for new types without standard library changes (including swift-evolution review)
  - The cases of the enum are poorly typed: there are cases like `.view` and `.color` which take NS/UIView or NS/UIColor instances, respectively, but since they’re in the standard library, they have to be typed as taking `Any` instead
  - The names of some of these enum cases do not seem to match Swift naming conventions)

To that end, I am proposing the following:

  - Deprecate PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 4.1 (including in the Swift 3 compatibility mode)
  - Remove PlaygroundQuickLook and CustomPlaygroundQuickLookable in Swift 5 to avoid including them in the stable ABI (this affects the compatibility modes, too)
  - Introduce a new protocol, CustomPlaygroundRepresentable, in the PlaygroundSupport library in Swift 4.1:

    protocol CustomPlaygroundRepresentable {
      /// Returns an alternate object or value which should stand in for the receiver in playground logging, or nil if the receiver’s default representation is preferred.
      var playgroundRepresentation: Any? { get }
    }

  - Update the PlaygroundLogger library in Swift 4.1 to support both CustomPlaygroundRepresentable and PlaygroundQuickLook/CustomPlaygroundQuickLookable
  - Provide a compatibility shim library which preserves PlaygroundQuickLook and CustomPlaygroundQuickLookable as deprecated in Swift 3/4 and unavailable in Swift 5, but only in playgrounds (including in the auxiliary source files stored inside a playground)

I’ve put a full proposal below. Please let me know what you think of this proposal; I’d like to get some feedback before taking this through the review process, but I’ll need to get that quickly so I can get it under review soon as this is targeted at Swift 4.1.

Thanks,
Connor

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