On Jan 11, 2018, at 11:22 AM, Connor Wakamo <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.

> 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.

>>> 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) 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.

> 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.


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.

>>> `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.

Fundamentally though, playground presentation is solving several different 

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.

>>> 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!


swift-evolution mailing list

Reply via email to