> On Jan 13, 2018, at 8:20 AM, Karl Wagner <razie...@gmail.com> wrote:
>> On 13. Jan 2018, at 03:22, Connor Wakamo via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>> On Jan 11, 2018, at 11:51 PM, Chris Lattner <clatt...@nondot.org 
>>> <mailto:clatt...@nondot.org>> wrote:
>>> On Jan 11, 2018, at 11:22 AM, Connor Wakamo <cwak...@apple.com 
>>> <mailto:cwak...@apple.com>> wrote:
>>>>>> 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 can think of a couple of cases where this could be useful.
>>>> The first is an enum. Extending Riley’s example from earlier in the thread:
>>>>    enum MyUnion {
>>>>            case string(String)
>>>>            case image(UIImage)
>>>>            case intPair(Int, Int)
>>>>            case none
>>>>    }
>>>> 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.
>>> 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.
>> Agreed.
>>>> The second case, and the one I’m more worried about, is subclassing. If, 
>>>> for instance, you have the following:
>>>>    class FooView: UIView, CustomPlaygroundRepresentable {
>>>>            var playgroundRepresentation: Any {
>>>>                    return “A string describing this view"
>>>>            }
>>>>    }
>>>>    class BarView: FooView {
>>>>            override var playgroundRepresentation: Any {
>>>>                    // BarView instances wanted to be logged as themselves, 
>>>> but there’s no way to express that
>>>>                    return ???
>>>>            }
>>>>    }
>>>> There’s nothing that BarView can do to ensure it gets logged like a view 
>>>> because FooView declared a conformance to CustomPlaygroundRepresentable.
>>> I really don’t understand this.  FooView declares that it conforms, and it 
>>> provides a “playgroundRepresentation” member.  Cool for FooView.
>>> 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.
>> 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.
>> 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:
>>      protocol CustomPlaygroundConvertible {
>>              var playgroundDescription: Any { get }
>>      }
>> 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.
> (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?
> 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).
> If it just returned “.default”, how would the logger know which superclass 
> implementation to use (UIView or NSObject)?

My answer to this is a bit of a deflection — this isn’t a case PlaygroundLogger 
currently has to worry about, as it’s “natively-supported” opaquely-represented 
types don’t have any common typing, aside from NSObject which isn’t 
opaquely-represented. But it does mean that if we introduce an API in 
PlaygroundSupport to request the “default” representation for an object, we may 
want to include a variant/overload of that which lets a developer clarify what 
they mean by “default”.

>>>>>> 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.
>>>> 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.
>>> 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.
>>> 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.
>>> 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 swift.org <http://swift.org/>) 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.
>> 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:
>>      func log(instance: Any) -> Data
>> 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 documented 
>> <https://github.com/apple/swift-xcode-playground-support/blob/4a73bf895b6fd9e5f72aad441869ab597e9e3fc3/PlaygroundLogger/Documentation/LoggerFormat.md>
>>  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.)
>> 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.)
> 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).
> 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).
>> 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.
>> 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.
> Lets say we have something like a formatter abstraction:
> /* sealed */ protocol PlaygroundLogEntry { }
> // We can expose concrete implementations of PlaygroundLogEntry as we like. 
> We might also have private entry formats.
> // It might also be an enum with private cases, when we get that feature.
> protocol PlaygroundFomatter {
>     func format(object: Any) -> PlaygroundLogEntry? // may fail if object 
> type is unexpected.
> }
> protocol CustomPlaygroundRepresentable {
>     var playgroundFormatter: PlaygroundFormatter? { get } // if nil, uses the 
> minimal, Mirror-based formatter.
> }
> 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.
> struct PNGImageFormatter: PlaygroundFormatter {
>     func format(object: Any) -> PlaygroundLogEntry? {
>         guard let imageData = object as? Data else { return nil } // 
> unexpected type.
>         // Parse the image data and return something the playground can 
> understand.
>     }
> }
> But it might also provide some optimised formatters for OS types, like UIView.
> struct UIViewFormatter: PlaygroundFormatter {
>     func format(object: Any) -> PlaygroundLogEntry? { /* magic */ }
> }
> extension UIView: CustomPlaygroundRepresentable {
>     static var defaultPlaygroundFormatter: PlaygroundFormatter { return 
> UIViewFormatter() }
>     var playgroundFormatter: PlaygroundFormatter? { return 
> UIView.defaultPlaygroundFormatter }
> }
> Also, one more handy formatter:
> struct KeyPathFormatter<T>: PlaygroundFormatter {
>     let base: PlaygroundFormatter
>     let dataKP: KeyPath<T, PlaygroundFormatter>
>     func format(object: Any) -> PlaygroundLogEntry? {
>         return base.format(object: object[keyPath: dataKP])
>     }
> }
> 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).
> class OpenGLView {
>     var formatter: PlaygroundFormatter? {
>         return KeyPathFormatter(base: 
> PlaygroundSupport.OpenGLTextureFormatter(format: self.textureFormat), 
> keyPath: \.backingTexture)
>     }
> }
> 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

I think this is a potentially interesting direction for exploration of how to 
expose hooks for PlaygroundLogger in the future. But as noted in my earlier 
reply to Chris, I think that the style of API I’m proposing here is still 
useful for the simple case, even if we want to expose this more complex support 
someday in the future.


>>>> Semi-related to this proposal, I’m working on a rewrite of the 
>>>> PlaygroundLogger library (currently at 
>>>> <https://github.com/cwakamo/swift-xcode-playground-support/tree/runtime-framework-and-improved-logger
>>>> <https://github.com/cwakamo/swift-xcode-playground-support/tree/runtime-framework-and-improved-logger>>)
>>>>  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.
>>> ****awesome****
>>> 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.
>> Yes, I very much look forward to being able to migrate off of Mirror when 
>> that time comes.
>>>>>> `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?
>>>> 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:
>>>>    enum Foo {
>>>>            case one(Any)
>>>>            case two
>>>>    }
>>>> 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.
>>> Why can’t an implementation just return “.two” instead of nil?
>>> 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.
>> 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.
>> 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:
>>      let foo = Foo()
>>      let bar = Bar()
>> produce the same log output (modulo things like type name).
>>> Fundamentally though, playground presentation is solving several different 
>>> problems:
>>> 1. Types with no special behavior can always be represented as strings, 
>>> which is handled by base protoocols.
>>> 2. Some types want custom string representations to show up in playgrounds, 
>>> but not in “print” and string interpolation.
>>> 3. Some types want to provide a 2D “quicklook” style presentation in 
>>> addition and beyond the string representation.
>>> 4. Fewer types want to provide an animated 2d representation, that is 
>>> either “live” or precomputed.  
>>> 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.
>> This is not quite accurate. All types have both a textual presentation and a 
>> “quicklook”-style presentation. I’d characterize this as:
>>      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.
>>      2. PlaygroundLogger then produces the data necessary to construct a 
>> “quicklook”-style presentation by doing the following:
>>              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.)
>>              b) Otherwise, PlaygroundLogger constructs a Mirror for the 
>> subject and produces structured log data representing the subject for a 
>> structural “quicklook”-style presentation.
>>      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.)
>> PlaygroundLogger does not support producing animated representations.
>> 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.
>> Connor
>>>>>> 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).
>>>> 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).
>>> Thank you!
>>> -Chris
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
swift-evolution mailing list

Reply via email to