<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class=""><br class=""><div><br class=""><blockquote type="cite" class=""><div class="">On 13. Jan 2018, at 03:22, Connor Wakamo via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class="Apple-interchange-newline"><br class=""><blockquote type="cite" class=""><div class="">On Jan 11, 2018, at 11:51 PM, Chris Lattner <<a href="mailto:clattner@nondot.org" class="">clattner@nondot.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;">On Jan 11, 2018, at 11:22 AM, Connor Wakamo <<a href="mailto:cwakamo@apple.com" class="">cwakamo@apple.com</a>> wrote:<br class=""><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><div class="">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.</div></div></div></div></blockquote><div class=""><br class=""></div>Which cases? How important are they?</div></div></div></blockquote><div class=""><br class=""></div><div class="">I can think of a couple of cases where this could be useful.</div><div class=""><br class=""></div><div class="">The first is an enum. Extending Riley’s example from earlier in the thread:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>enum MyUnion {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>case string(String)</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>case image(UIImage)</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>case intPair(Int, Int)</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>case none</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class=""><br class=""></div><div class="">This enum might want to present the string and image cases as strings and images, but treat the intPair and none cases the “default” way for the enum. This is probably not the most compelling example as there is a workaround — return a second enum or other structured type from playgroundRepresentation — but that feels not great.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">IMO, this is sugaring something that doesn’t need to be sugared. There are simple solutions to this problem without complicating the design of your feature. The few people who care about this can write it out long hand.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">Agreed.</div><br class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><div class="">The second case, and the one I’m more worried about, is subclassing. If, for instance, you have the following:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>class FooView: UIView, CustomPlaygroundRepresentable {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>var playgroundRepresentation: Any {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>return “A string describing this view"</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>class BarView: FooView {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>override var playgroundRepresentation: Any {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// BarView instances wanted to be logged as themselves, but there’s no way to express that</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>return ???</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class=""><br class=""></div><div class="">There’s nothing that BarView can do to ensure it gets logged like a view because FooView declared a conformance to CustomPlaygroundRepresentable.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">I really don’t understand this. FooView declares that it conforms, and it provides a “playgroundRepresentation” member. Cool for FooView.</div><div class=""><br class=""></div><div class="">BarView comes around and overrides FooView’s implementation. They don’t have to conform, because it *isa* FooView, so of course it conforms. If it wants to customize its presentation, it overrides its “playgroundRepresentation” method and it… just works. If it conditionally wants the FooView representation for some reason, it can even call “super.playgroundRepresentation”. What’s the problem? This seems ideal to me.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">The issue is that there’s no way for `BarView` to recover the default playground representation which `UIView` and its subclasses get. For a `UIView`, the PlaygroundLogger library renders an image of the view and packages that up in a log entry. If `BarView` wants that instead of `FooView`’s custom representation, there’s no way for it to request it.</div><div class=""><br class=""></div><div class="">That being said, I think I’m convinced that this is enough of an edge case that shouldn’t complicate the design of the protocol. So in my updated proposal I’ll be going with:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>protocol CustomPlaygroundConvertible {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>var playgroundDescription: Any { get }</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class=""><br class=""></div><div class="">If we find out that this isn’t as much of an edge case as thought, we can add a type like `DefaultPlaygroundRepresentation<Wrapped>` to PlaygroundSupport which would signal to the playground logger that it should log the wrapped type but without considering a `CustomPlaygroundConvertible` conformance.</div></div></div></blockquote><div><br class=""></div><div>(Replying to future comments as well here): so the hierarchy looks like: NSObject -> UIView -> FooView -> BarView, and we’re saying that BarView should be able to override FooView’s override and drop down one of its ancestors implementations?</div><div><br class=""></div><div>IMO, the only way to do this is to return “super.super.playgroundDescription” (if BarView wants to present as a UIView) or “super.super.super.playgroundDescription” (if it wants to present as an NSObject — imagine NSObject had some custom representation).</div><div><br class=""></div><div>If it just returned “.default”, how would the logger know which superclass implementation to use (UIView or NSObject)?</div><br class=""><blockquote type="cite" class=""><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><div class="">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 <a href="https://github.com/apple/swift-xcode-playground-support" class="">swift-xcode-playground-support</a>) 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`.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">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.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">It’s a feature that Optional (and other standard library/corelibs/OS types) don’t conform to CustomPlaygroundRepresentable. In my mind, it’s the role of the PlaygroundLogger library to understand the types for which it wishes to generate an opaque representation instead of the standard/fallback structured representation. So Optional, String, Int, UIColor, NSView, etc. don’t themselves conform to CustomPlaygroundRepresentable — they’re not customizing their presentation in a playground.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">IMO, this was the right Swift 1 attitude: "make it work at all costs" but this is not the right approach for Swift over the long term, and not the right answer for Swift 5 in particular.</div><div class=""><br class=""></div><div class="">Swift is still very young and immature in some ways, but it it is intentionally design for extensibility and to be used in surprising and delightful ways. It isn’t an accident of history that Int and Bool are defined in the standard library instead of being burned into the compiler.</div><div class=""><br class=""></div><div class="">IMO, every time you choose to privilege “well known” types in the standard library with special code in Xcode (or some other high level system loosely integrated with<span class="Apple-converted-space"> </span><a href="http://swift.org/" class="">swift.org</a>) you are introducing technical debt into the Swift ecosystem. This is because you are violating the basic principles on which Swift was established, which is that users can [re]define primitives to build their own abstractions and carve out new domains beyond what was originally envisioned when you’re writing the UI code.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">I am trying to figure out a way to restructure this without PlaygroundLogger knowing about the core types but am at a loss. PlaygroundLogger is fundamentally this function:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>func log(instance: Any) -> Data</div><div class=""><br class=""></div><div class="">where the returned `Data` contains a well-formed log packet containing either a structured or opaque log entry. Structured log entries come “for free” thanks to Mirror, so the opaque log entry case is the only interesting one. Opaque log entries have <a href="https://github.com/apple/swift-xcode-playground-support/blob/4a73bf895b6fd9e5f72aad441869ab597e9e3fc3/PlaygroundLogger/Documentation/LoggerFormat.md" class="">documented</a> format for the kinds of data which they could contain, so conceivably an API could be exposed that would allow a type to return a `Data` matching that format. That wouldn’t be great, though, as it would mean that the format would become API instead of something negotiated between PlaygroundLogger and its clients. (Since that’s a reasonable internal implementation strategy, though, that’s how I model things in the new PlaygroundLogger implementation.)</div><div class=""><br class=""></div><div class="">So if PlaygroundLogger isn’t going to accept raw `Data` values as input, it needs to take *something*. I don’t think it makes a ton of sense for PlaygroundLogger/PlaygroundSupport to define a bunch of intermediate types, nor do I think it is PlaygroundLogger/PlaygroundSupport’s role to define protocols for what it means to be an image, color, view, integer, etc. (Perhaps that’s the sticking point here?) And even if it did provide those protocols, there’d be an issue surrounding how PlaygroundLogger would choose an implementation if a type implemented multiple of these core protocols. (I discuss that briefly as a rejected alternative, as my first thinking for this problem was that I should introduce a suite of protocols like `CustomNSViewConvertible`, `CustomIntConvertible`, `CustomUIColorConvertible`, and so on.)</div></div></div></blockquote><div><br class=""></div><div>I don’t think it needs to define protocols for what it means to be an image, but it could define a general “playground log formatter” abstraction which can translate an object in to a log entry (with a default implementation based on Mirror).</div><div><br class=""></div><div>That could be a way to solve the problem above. The core types could simply expose formatter objects, and CustomPlaygroundRepresentable could just ask each instance explicitly for the formatter it wants to use (falling-back to the Mirror-based one if the custom formatter fails or the object asks explicitly for no special formatting).</div><br class=""><blockquote type="cite" class=""><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class=""><br class=""></div><div class="">Previously, the standard library and PlaygroundLogger solved this with the PlaygroundQuickLook enum. For the reasons listed in my proposal, I think that the PlaygroundQuickLook enum is harmful, so I wouldn’t want to solve this problem that way.</div><div class=""><br class=""></div><div class="">So given that, I think it’s not unreasonable for a platform/toolchain’s playground logger to know about that platform/toolchain’s important types so it can produce the appropriate opaque log entries. If there’s some approach that I’m overlooking — or if I’m dismissing the suite of protocols case too quickly — I would love to hear it, because I’m open to suggestions.</div></div></div></blockquote><br class=""></div><div>Lets say we have something like a formatter abstraction:</div><div><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><font face="Courier" class="">/* sealed */ protocol PlaygroundLogEntry { }</font></div><div><font face="Courier" class=""><br class=""></font></div><div><font face="Courier" class="">// We can expose concrete implementations of PlaygroundLogEntry as we like. We might also have private entry formats.</font></div><div><font face="Courier" class="">// It might also be an enum with private cases, when we get that feature.</font></div><div><font face="Courier" class=""><br class=""></font></div><div><font face="Courier" class="">protocol PlaygroundFomatter {</font></div><div><font face="Courier" class=""> func format(object: Any) -> PlaygroundLogEntry? // may fail if object type is unexpected.</font></div><div><font face="Courier" class="">}</font></div><div><font face="Courier" class=""><br class=""></font></div><div><font face="Courier" class="">protocol CustomPlaygroundRepresentable {</font></div><div><font face="Courier" class=""> var playgroundFormatter: PlaygroundFormatter? { get } // if nil, uses the minimal, Mirror-based formatter.</font></div><div><font face="Courier" class="">}</font></div></blockquote><div><br class=""></div><div>PlaygroundLogger/Support can build in some basic formatters for stuff like some PNG-formatted bytes, RGB colours, OpenGL textures. Stuff which is reasonably useful across platforms.</div><div><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><font face="Courier" class="">struct PNGImageFormatter: PlaygroundFormatter {</font></div><div><font face="Courier" class=""> func format(object: Any) -> PlaygroundLogEntry? {</font></div><div><font face="Courier" class=""> guard let imageData = object as? Data else { return nil } // unexpected type.</font></div><div><font face="Courier" class=""> // Parse the image data and return something the playground can understand.</font></div><div><font face="Courier" class=""> }</font></div><div><font face="Courier" class="">}</font></div></blockquote><div><br class=""></div><div>But it might also provide some optimised formatters for OS types, like UIView.</div><div><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><div><font face="Courier" class="">struct UIViewFormatter: PlaygroundFormatter {</font></div></div><div><div><font face="Courier" class=""> func format(object: Any) -> PlaygroundLogEntry? { /* magic */ </font><span style="font-family: Courier;" class="">}</span></div></div><div><div><font face="Courier" class="">}</font></div><div><font face="Courier" class=""><br class=""></font></div><div><font face="Courier" class="">extension UIView: CustomPlaygroundRepresentable {</font></div></div></blockquote><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><div><font face="Courier" class=""> static var defaultPlaygroundFormatter: PlaygroundFormatter { return UIViewFormatter() }</font></div><div><font face="Courier" class=""><br class=""></font></div><div><font face="Courier" class=""> var playgroundFormatter: PlaygroundFormatter? { return UIView.defaultPlaygroundFormatter }</font></div></div></blockquote><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><div><font face="Courier" class="">}</font></div></div></blockquote><div><br class=""></div><div>Also, one more handy formatter:</div><div><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><font face="Courier" class="">struct KeyPathFormatter<T>: PlaygroundFormatter {</font></div><div><font face="Courier" class=""> let base: PlaygroundFormatter</font></div><div><font face="Courier" class=""> let dataKP: KeyPath<T, PlaygroundFormatter></font></div><div><font face="Courier" class=""><br class=""></font></div><div><font face="Courier" class=""> func format(object: Any) -> PlaygroundLogEntry? {</font></div><div><font face="Courier" class=""> return base.format(object: object[keyPath: dataKP])</font></div><div><font face="Courier" class=""> }</font></div><div><font face="Courier" class="">}</font></div></blockquote><div><br class=""></div><div>Then, when you’re porting the rich graphical previews to a new platform, you can map your base-level UI objects in terms the formatter knows about, or write your own for any public log-entry formats (if there are any).</div><div><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><font face="Courier" class="">class OpenGLView {</font></div><div><font face="Courier" class=""> var formatter: PlaygroundFormatter? {</font></div><div><font face="Courier" class=""> return KeyPathFormatter(base: PlaygroundSupport.OpenGLTextureFormatter(format: self.textureFormat), keyPath: \.backingTexture)</font></div><div><font face="Courier" class=""> }</font></div><div><font face="Courier" class="">}</font></div></blockquote><div><br class=""></div><div>This way, sure, PlaygroundLogger/Support has some built-in behaviours for OS types it knows about. That said, I suppose it would be open for anybody to add their own if it’s useful for the community. We don’t have a way in Swift yet to make sealed protocols, but an enum with private cases might allow us to model this well. Nobody is going to switch over it, so it should be fine :P</div><div><br class=""></div><div><blockquote type="cite" class=""><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><div class="">Semi-related to this proposal, I’m working on a rewrite of the PlaygroundLogger library (currently at <<a href="https://github.com/cwakamo/swift-xcode-playground-support/tree/runtime-framework-and-improved-logger" class="">https://github.com/cwakamo/swift-xcode-playground-support/tree/runtime-framework-and-improved-logger</a>>) which makes it so that the only special standard library behavior it depends on is Mirror — it no longer relies on the Swift runtime (via PlaygroundQuickLook(reflecting:)) to figure out what to log, instead opting to check protocol conformances itself. So if this proposal is accepted into Swift, concurrent with that we’ll have a new PlaygroundLogger implementation which gets rid of as many hacks as possible.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">****awesome****</div><div class=""><br class=""></div><div class="">I hope that someday when we have a better API than Mirror (e.g. that supports mutation) that allows reflecting on stored properties, methods, and all of the other things that Swift needs to eventually support that you’ll switch to it. I understand that the glorious future isn’t very helpful to you today though.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">Yes, I very much look forward to being able to migrate off of Mirror when that time comes.</div><br class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><div class=""><div class="">`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.</div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">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?</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">I think the big difference with CustomStringConvertible is that it’s possible for a conformance to reimplement the default behavior on its own. For instance, if I have:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>enum Foo {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>case one(Any)</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>case two</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class=""><br class=""></div><div class="">As noted above, recovering the default behavior with CustomPlaygroundRepresentable is not always possible if the return type is `Any`. That very well might be an acceptable trade-off to keep the API simple.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">Why can’t an implementation just return “.two” instead of nil?</div><div class=""><br class=""></div><div class="">Is the problem the “one” case? If that is the problem, then it might be better to take a completely different approach where you embrace the fact that you have structured data producing 2D results that need to be displayed. Each result could either return an atomic result or a result that wraps some other recursive 2D presentation.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">No, the issue is that the playground logger would see that `Foo.two` conforms to `CustomPlaygroundConvertible`, and would therefore call `playgroundDescription` another time, which would return another `Foo.two`, and this would continue endlessly. I plan to guard against that in PlaygroundLogger with a limit on chaining, but this protocol should not require a failsafe as a feature.</div><div class=""><br class=""></div><div class="">This chaining is so that `Foo` can return a value of `Bar` which itself conforms to `CustomPlaygroundConvertible` — the promise of the API is that a value will be logged as if it were the value returned from `playgroundDescription`, so that the following lines:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>let foo = Foo()</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>let bar = Bar()</div><div class=""><br class=""></div><div class="">produce the same log output (modulo things like type name).</div><br class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><div class="">Fundamentally though, playground presentation is solving several different problems:</div><div class=""><br class=""></div><div class="">1. Types with no special behavior can always be represented as strings, which is handled by base protoocols.</div><div class="">2. Some types want custom string representations to show up in playgrounds, but not in “print” and string interpolation.</div><div class="">3. Some types want to provide a 2D “quicklook” style presentation in addition and beyond the string representation.</div><div class="">4. Fewer types want to provide an animated 2d representation, that is either “live” or precomputed. </div><div class=""><br class=""></div><div class="">I’d suggest that this protocol only tackle problems 2 and 3, but you should cleanly distinguish between them, probably with a bespoke enum or struct that models these capabilities.</div></div></div></div></blockquote><div class=""><br class=""></div><div class="">This is not quite accurate. All types have both a textual presentation and a “quicklook”-style presentation. I’d characterize this as:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>1. PlaygroundLogger determines if the subject being logged conforms to `CustomPlaygroundConvertible`. If so, it calls `playgroundDescription` and starts over, treating the return value as its subject.</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>2. PlaygroundLogger then produces the data necessary to construct a “quicklook”-style presentation by doing the following:</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>a) If the subject is a type known to PlaygroundLogger (modeled as a conformance to a PlaygroundLogger-internal protocol), then PlaygroundLogger produces opaque log data for that type’s “quicklook”-style presentation. (As examples: for String, it encodes UTF-8 bytes; for CGPoint, it encodes the x and y points using an NSKeyedArchiver; for UIView, it encodes a rendered image; etc.)</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>b) Otherwise, PlaygroundLogger constructs a Mirror for the subject and produces structured log data representing the subject for a structural “quicklook”-style presentation.</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>3. PlaygroundLogger then uses `String(describing:)` on the subject being logged to produce the textual representation. (In some cases, IDEs may ignore this value in favor of textual-ish information about the “quicklook”-style presentation, such as color swatch or the dimensions of the logged image.)</div><div class=""><br class=""></div><div class="">PlaygroundLogger does not support producing animated representations.</div><div class=""><br class=""></div><div class="">The `CustomPlaygroundConvertible` protocol is all-or-nothing; if you implement it, both your textual and “quicklook”-style presentations are modified to match whatever you returned. There isn’t a facility to only affect one presentation or the other. This provides parity with `CustomPlaygroundQuickLookable` but with a more flexible interface, which is the intent of this proposal.</div><div class=""><br class=""></div><div class="">Connor</div><br class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><div class="">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.)</div></div></div></div></blockquote><br class=""></div><div class="">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).</div></div></div></blockquote><div class=""><br class=""></div><div class="">That’s very reasonable. I’ll update the proposal to use CustomPlaygroundConvertible (unless I or someone else can come up with a really good “Thing” for a name like CustomPlaygroundThingConvertible, as that would even better match CustomStringConvertible/CustomDebugStringConvertible).</div></div></div></div></blockquote><br class=""></div><div class="">Thank you!</div><div class=""><br class=""></div><div class="">-Chris</div><div class=""><br class=""></div><br class=""></div></div></blockquote></div><br class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">_______________________________________________</span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">swift-evolution mailing list</span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a></span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a></span></div></blockquote></div><br class=""></body></html>