On 13.09.2017 19:03, Tony Allevato wrote:


On Wed, Sep 13, 2017 at 8:41 AM Vladimir.S via swift-evolution <[email protected] <mailto:[email protected]>> wrote:

    On 13.09.2017 7:14, Xiaodi Wu via swift-evolution wrote:
     >
     > On Tue, Sep 12, 2017 at 22:07 Tony Allevato <[email protected]
    <mailto:[email protected]>
     > <mailto:[email protected] <mailto:[email protected]>>> wrote:
     >
     >     On Tue, Sep 12, 2017 at 7:10 PM Xiaodi Wu <[email protected]
    <mailto:[email protected]>
     >     <mailto:[email protected] <mailto:[email protected]>>> wrote:
     >
     >         On Tue, Sep 12, 2017 at 9:58 AM, Thorsten Seitz via 
swift-evolution
     >         <[email protected] <mailto:[email protected]>
    <mailto:[email protected] <mailto:[email protected]>>> 
wrote:
     >
     >             Good arguments, Tony, you have convinced me on all points.
    Transient is
     >             the way to go. Thank you for your patience!
     >
     >
     >         On many points, I agree with Tony, but I disagree that 
"transient"
    addresses
     >         the issue at hand. The challenge being made is that, as Gwendal 
puts
    it, it's
     >         _unwise_ to have a default implementation, because people might 
forget
    that
     >         there is a default implementation. "Transient" only works if you 
remember
     >         that there is a default implementation, and in that case, we 
already
    have a
     >         clear syntax for overriding the default.
     >
     >
     >     RightБ─■I hope it hasn't sounded like I'm conflating the two concepts
    completely.
     >     The reason I brought up "transient" is because nearly all of the 
"risky"
    examples
     >     being cited so far have been of the variety "I have a type where some
    properties
     >     happen to be Equatable but shouldn't be involved in equality", so my 
intention
     >     has been to show that if we have a better solution to that specific 
problem
     >     (which is, related to but not the same as the question at hand), 
then there
     >     aren't enough risky cases left to warrant adding this level of 
complexity
    to the
     >     protocol system.
     >
     >
     >         As others point out, there's a temptation here to write things 
like
     >         "transient(Equatable)" so as to control the synthesis of
    implementations on a
     >         per-protocol basis. By that point, you've invented a whole new 
syntax for
     >         implementing protocol requirements. (Ah, you might say, but it's 
hard to
     >         write a good hashValue implementation: sure, but that's 
adequately
    solved by
     >         a library-supplied combineHashes() function.)
     >
     >
     >     I totally agree with this. A design that would try to annotate 
"transient"
    with a
     >     protocol or list of protocols is missing the point of the semantics 
that
     >     "transient" is supposed to provide. It's not a series of switches to 
that
    can be
     >     flipped on and off for arbitrary protocolsБ─■it's a semantic tag 
that assigns
     >     additional meaning to properties and certain protocols (such as 
Equatable,
     >     Hashable, and Codable, but possibly others that haven't been 
designed yet)
    would
     >     have protocol-specific behavior for those properties.
     >
     >     To better explain what I've been poking at, I'm kind of 
extrapolating this
    out to
     >     a possible future where it may be possible to more generally (1) 
define custom
     >     @attributes in Swift, like Java annotations, and then (2) use some
     >     metaprogramming constructs to generate introspective default
    implementations for
     >     a protocol at compile-time just as the compiler does "magically" 
now, and the
     >     generator would be able to query attributes that are defined by the 
same
    library
     >     author as the protocol and handle them accordingly.
     >
     >     In a world where that's possible, I think it's less helpful to think 
in
    terms of
     >     "I need to distinguish between conforming to X and getting a 
synthesized
     >     implementation and conforming to X and avoiding the synthesized 
implementation
     >     because the default might be risky", but instead to think in terms of 
"How
    can I
     >     provide enough semantic information about my types to remove the 
risk?"
     >
     >     In other words, the switches we offer developers to flip shouldn't 
be about
     >     turning on/off entire features, but about giving the compiler enough
    information
     >     to make it smart enough that we never need to turn it off in the 
first
    place. As
     >     I alluded to before, if I have 10 properties in a type and only 1 of 
those
    needs
     >     to be ignored in ==/hashValue/whatever, writing "Equatable" instead 
of
    "derives
     >     Equatable" isn't all that helpful. Yes, it spits out an error message
    where there
     >     wouldn't have been one, but it doesn't reduce any of the burden of 
having to
     >     provide the appropriate manual implementation.
     >
     >     But all that stuff about custom attributes and metaprogramming
    introspection is a
     >     big topic of it's own that isn't going to be solved in Swift 5, so 
this is
    a bit
     >     of a digression. :)
     >
     >
     > That said, we could have enums EquatingKeys and HashingKeys, a la
    CodingKeys... That
     > may not be a huge leap to propose and implement.

    Actually, not taking into account a question of explicit marker for 
auto-generated
    methods, this is IMO a great point.

    Codable, which can auto-generate methods, *had* these CodingKeys from the 
moment of
    birth. Currently, we have a proposal for auto-generating of methods for
    Equatable/Hashable. Why we don't have a EquatingKeys/HashingKeys option for 
them in
    symmetry with Codable? Why Codable already has a method to exclude fields, 
but for
    Equatable/Hashable we are discussing some future esoteric '@transient' 
modifier(which
    should describe the behaviour and destination of the property in details 
for compiler
    and conformed protocols so all will "just work") ?
    How this future '@transient' will live together with current CodingKeys ?

    IMO the right solution will be:
    1. introduce 'deriving'-like keyword to explicitly express that you request 
an
    auto-synthesize of protocol requirements
    2. introduce EquatingKeys/HashingKeys to be able to say which properties 
should be
    included in generated requirements
    3. Think what kind of '@transient' marker could be introduced in future to 
replace
    the using of CodingKeys/EquatingKeys/HashingKeys.


I don't agree with Xiaodi here that a hypothetical EquatableKey/HashableKey would be the same thing as CodingKey.

CodingKey embeds other semantic information, like the string or integer key to use when encoding/decoding a value in a container. The cases of a CodingKey enum are also actual values that are passed around to other encoder/decoder methods—they're not *just* there for the synthesized code.


Can't agree. Yes, they are not *just* for the synthesized code, they have additional meaning for _Codable_ protocol. But if you read this https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types , first you find regarding CodingKeys is :
"
Choose Properties to Encode and Decode Using Coding Keys

When this enumeration is present, its cases serve as the authoritative list of properties that must be included when instances of a codable type are encoded or decoded. The names of the enumeration cases should match the names you've given to the corresponding properties in your type.

Omit properties from the CodingKeys enumeration if they won't be present when decoding instances, or if certain properties shouldn't be included in an encoded representation.
"

So I'd say, that exclusion of fields from participate in Codable, is main or at least high important role of CodingKeys. And I can't see why EquatableKeys/HashableKeys can't play the same role(without additional meaning CodingKeys has for Codable).

Vladimir.


Equatable/HashableKeys wouldn't be that. The cases would never be used anywhere else, passed anywhere, or returned from anything. It would just be a list of cases that match property names, which doesn't really feel like the right model here. Simply put, if the values aren't useful *as values*, they shouldn't be modeled as values.

I also don't see how "transient" would ever replace CodingKeys. Instead, it would augment it. Right now, the compiler autogenerates CodingKeys if you don't provide one. What "transient" would do is affect *what* the compiler generates for you there; likewise with Equatable and Hashable. They're not identical concepts.

    Vladimir.

     >
     >             -Thorsten
     >
     >             Am 12.09.2017 um 16:38 schrieb Tony Allevato via 
swift-evolution
     >             <[email protected] <mailto:[email protected]>
    <mailto:[email protected] <mailto:[email protected]>>>:
     >
     >>
     >>
     >>             On Mon, Sep 11, 2017 at 10:05 PM Gwendal Rouц╘
    <[email protected] <mailto:[email protected]>
     >>             <mailto:[email protected] 
<mailto:[email protected]>>>
    wrote:
     >>
     >>>
     >>>>                     This doesn't align with how Swift views the role 
of
     >>>>                     protocols, though. One of the criteria that the 
core
    team has
     >>>>                     said they look for in a protocol is "what generic
    algorithms
     >>>>                     would be written using this protocol?" 
AutoSynthesize
    doesn't
     >>>>                     satisfy thatБ─■there are no generic algorithms 
that you
    would
     >>>>                     write with AutoEquatable that differ from what 
you would
     >>>>                     write with Equatable.
     >>>
     >>>                     And so everybody has to swallow implicit and 
non-avoidable
     >>>                     code synthesis and shut up?
     >>>
     >>>
     >>>                 That's not what I said. I simply pointed out one of 
the barriers
     >>>                 to getting a new protocol added to the language.
     >>>
     >>>                 Code synthesis is explicitly opt-in and quite
    avoidableБ─■you either
     >>>                 don't conform to the protocol, or you conform to the
    protocol and
     >>>                 provide your own implementation. What folks are 
differing on is
     >>>                 whether there should have to be *two* explicit 
switches that you
     >>>                 flip instead of one.
     >>
     >>                 No. One does not add a protocol conformance by whim. 
One adds a
     >>                 protocol conformance by need. So the conformance to the
    protocol is
     >>                 a *given* in our analysis of the consequence of code
    synthesis. You
     >>                 can not say "just don't adopt it".
     >>
     >>                 As soon as I type the protocol name, I get synthesis. 
That's the
     >>                 reason why the synthesized code is implicit. The 
synthesis is
     >>                 explicitly written in the protocol documentation, if you
    want. But
     >>                 not in the programmer's code.
     >>
     >>                 I did use "non-avoidable" badly, you're right: one can 
avoid
    it, by
     >>                 providing its custom implementation.
     >>
     >>                 So the code synthesis out of a mere protocol adoption 
*is*
    implicit.
     >>
     >>>                 Let's imagine a pie. The whole pie is the set of all 
Swift
    types.
     >>>                 Some slice of that pie is the subset of those types 
that satisfy
     >>>                 the conditions that allow one of our protocols to be
    synthesized.
     >>>                 Now that slice of pie can be sliced again, into the 
subset of
     >>>                 types where (1) the synthesized implementation is 
correct
    both in
     >>>                 terms of strict value and of business logic, and (2) 
the subset
     >>>                 where it is correct in terms of strict value but is 
not the
    right
     >>>                 business logic because of something like transient 
data.
     >>
     >>                 Yes.
     >>
     >>>                 What we have to consider is, how large is slice (2) 
relative to
     >>>                 the whole pie, *and* what is the likelihood that 
developers are
     >>>                 going to mistakenly conform to the protocol without 
providing
     >>>                 their own implementation, *and* is the added 
complexity worth
     >>>                 protecting against this case?
     >>
     >>                 That's quite a difficult job: do you think you can 
evaluate this
     >>                 likelihood?
     >>
     >>                 Explicit synthesis has big advantage: it avoids this 
question
    entirely.
     >>
     >>                 Remember that the main problem with slide (2) is that 
developers
     >>                 can not *learn* to avoid it.
     >>
     >>                 For each type is slide (2) there is a probability that 
it comes
     >>                 into existence with a forgotten explicit protocol 
adoption. And
     >>                 this probability will not go down as people learn Swift 
and
     >>                 discover the existence of slide (2). Why? because this
    probability
     >>                 is driven by unavoidable human behaviors:
     >>                 - developer doesn't see the problem (a programmer 
mistake)
     >>                 - the developper plans to add explicit conformance 
later and
     >>                 happens to forget (carelessness)
     >>                 - a developper extends an existing type with a transient
    property,
     >>                 and doesn't add the explicit protocol conformance that 
has become
     >>                 required.
     >>
     >>                 Case 2 and 3 bite even experienced developers. And they 
can't be
     >>                 improved by learning.
     >>
     >>                 Looks like the problem is better defined as an 
ergonomics
    issue, now.
     >>
     >>>                 If someone can show me something that points to 
accidental
     >>>                 synthesized implementations being a significant 
barrier to
    smooth
     >>>                 development in Swift, I'm more than happy to consider 
that
     >>>                 evidence. But right now, this all seems hypothetical 
("I'm
    worried
     >>>                 that...") and what's being proposed is adding 
complexity to the
     >>>                 language (an entirely new axis of protocol 
conformance) that
    would
     >>>                 (1) solve a problem that may not exist to any great 
degree, and
     >>>                 (2) does not address the fact that if that problem 
does indeed
     >>>                 exist, then the same problem just as likely exists 
with certain
     >>>                 non-synthesized default implementations.
     >>
     >>                 There is this sample code by Thorsten Seitz with a 
cached
    property
     >>                 which is quite simple and clear :
     >>
    
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170911/039684.html
     >>
     >>                 This is the sample code that had me enter the "worried" 
camp.'
     >>
     >>
     >>             I really like Thorsten's example, because it actually 
proves that
     >>             requiring explicit derivation is NOT the correct approach 
here.
    (Let's
     >>             set aside the fact that Optionals prevent synthesis because 
we don't
     >>             have conditional conformances yet, and assume that we've 
gotten that
     >>             feature as well for the sake of argument.)
     >>
     >>             Let's look at two scenarios:
     >>
     >>             1) Imagine I have a value type with a number of simple 
Equatable
     >>             properties. In a world where synthesis is explicit, I tell 
that value
     >>             type to "derive Equatable". Everything is fine. Later, I 
decide
    to add
     >>             some cache property like in Thorsten's example, and that 
property
    just
     >>             happens to also be Equatable. After doing so, the correct 
thing to do
     >>             would be to remove the "derive" part and provide my custom
     >>             implementation. But if I forget to do that, the synthesized 
operator
     >>             still exists and applies to that type. If you're arguing that 
"derive
     >>             Equatable" is better because its explicitness prevents 
errors,
    you must
     >>             also accept that there are possibly just as many cases 
where that
     >>             explicitness does *not* prevent errors.
     >>
     >>             2) Imagine I have a value type with 10 Equatable properties 
and one
     >>             caching property that also happens to be Equatable. The 
solution
    being
     >>             proposed here says that I'm better off with explicit 
synthesis
    because
     >>             if I conform that type to Equatable without "derive", I get 
an error,
     >>             and then I can provide my own custom implementation. But I 
have to
     >>             provide that custom implementation *anyway* to ignore the 
caching
     >>             property even if we don't make synthesis explicit. Making 
it explicit
     >>             hasn't saved me any workБ─■it's only given me a compiler 
error for a
     >>             problem that I already knew I needed to resolve. If we tack 
on
    Hashable
     >>             and Codable to that type, then I still have to write a 
significant
     >>             amount of boilerplate for those custom operations. 
Furthermore, if
     >>             synthesis is explicit, I have *more* work because I have to
    declare it
     >>             explicitly even for types where the problem above does not 
occur.
     >>
     >>             So, making derivation explicit is simply a non-useful dodge 
that
     >>             doesn't solve the underlying problem, which is this: 
Swift's type
     >>             system currently does not distinguish between Equatable
    properties that
     >>             *do* contribute to the "value" of their containing instance 
vs.
     >>             Equatable properties that *do not* contribute to the 
"value" of their
     >>             containing instance. It's the difference between behavior 
based on a
     >>             type and additional business logic implemented on top of 
those types.
     >>
     >>             So, what I'm trying to encourage people to see is this: saying 
"there
     >>             are some cases where synthesis is risky because it's 
incompatible
    with
     >>             certain semantics, so let's make it explicit everywhere" is 
trying to
     >>             fix the wrong problem. What we should be looking at is 
*"how do
    we give
     >>             Swift the additional semantic information it needs to make 
the
     >>             appropriate decision about what to synthesize?"*
     >>
     >>             That's where concepts like "transient" come in. If I have an
     >>             Equatable/Hashable/Codable type with 10 properties and one 
cache
     >>             property, I *still* want the synthesis for those first 10
    properties. I
     >>             don't want the presence of *one* property to force me to 
write all of
     >>             that boilerplate myself. I just want to tell the compiler 
which
     >>             properties to ignore.
     >>
     >>             Imagine you're a stranger reading the code to such a type 
for the
    first
     >>             time. Which would be easier for you to quickly understand? 
The
    version
     >>             with custom implementations of ==, hashValue, init(from:), 
and
     >>             encode(to:) all covering 10 or more properties that you 
have to read
     >>             through to figure out what's being ignored (and make sure 
that the
     >>             author has done so correctly), or the version that conforms 
to those
     >>             protocols, does not contain a custom implementation, and 
has each
     >>             transient property clearly marked? The latter is more 
concise and
     >>             "transient" carries semantic weight that gets buried in a 
handwritten
     >>             implementation.
     >>
     >>             Here's a fun exerciseБ─■you can actually write something 
like
    "transient"
     >>             without any additional language support today:
     >> https://gist.github.com/allevato/e1aab2b7b2ced72431c3cf4de71d306d. A
     >>             big drawback to this Transient type is that it's not as 
easy to
    use as
     >>             an Optional because of the additional sugar that Swift 
provides
    for the
     >>             latter, but one could expand it with some helper properties 
and
    methods
     >>             to sugar it up the best that the language will allow today.
     >>
     >>             I would wager that this concept, either as a wrapper type 
or as a
     >>             built-in property attribute, would solve a significant 
majority of
     >>             cases where synthesis is viewed to be "risky". If we accept 
that
     >>             premise, then we can back to our slice of pie and all we're 
left with
     >>             in terms of "risky" types are "types that contain 
properties that
     >>             conform to a certain protocol but are not really transient 
but also
     >>             shouldn't be included verbatim in synthesized operations". 
I'm
     >>             struggling to imagine a type that fits that description, so 
if
    they do
     >>             exist, it's doubtful that they're a common enough problem 
to warrant
     >>             introducing more complexity into the protocol conformance 
system.
     >>
     >>
     >>                 Gwendal
     >>
     >>             _______________________________________________
     >>             swift-evolution mailing list
     >> [email protected] <mailto:[email protected]>
    <mailto:[email protected] <mailto:[email protected]>>
     >> https://lists.swift.org/mailman/listinfo/swift-evolution
     >
     >             _______________________________________________
     >             swift-evolution mailing list
     > [email protected] <mailto:[email protected]>
    <mailto:[email protected] <mailto:[email protected]>>
     > https://lists.swift.org/mailman/listinfo/swift-evolution
     >
     >
     >
     > _______________________________________________
     > swift-evolution mailing list
     > [email protected] <mailto:[email protected]>
     > https://lists.swift.org/mailman/listinfo/swift-evolution
     >
    _______________________________________________
    swift-evolution mailing list
    [email protected] <mailto:[email protected]>
    https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to