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

Reply via email to