On Thu, Sep 7, 2017 at 5:37 AM Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:
Sent from my iPad
On Sep 7, 2017, at 7:07 AM, Gwendal Roué via swift-evolution < > swift-evolution@swift.org> wrote:
Hello,
I'm interested in this debate because I've been bitten by automatic
synthesis recently.
I'm reading your discussion, but I don't have a strong opinion. I only
have an anecdote: implicit, automatic, and unavoidable code synthesis code
can make it difficult to write some DSLs (aka Domain-Specific Languages).
I did stumble against undesired Equatable synthesis while developping a
library[1] that generates SQL snippets. In this library, the `==` operator
does not return a Bool: it returns an SQL expression:
// SELECT * FROM players WHERE bestScore = 1000
Player.filter(bestScore == 1000)
Since the library is free to define == as an operator that returns an SQL
expression, this works quite well. Even when both operands have the same
type:
// SELECT * FROM players WHERE lastScore = bestScore
Player.filter(lastScore == bestScore)
However, as soon as the type is (also) Equatable, an ambiguity occurs, and
the DSL is basically broken:
Player.filter(lastScore == bestScore) // which == please?
In this case, the == from synthesized Equatable conformance is not welcome
at all. It prevents the library from controlling the return type of the ==
operator. Equatable conformance is unavoidable for enums based on String or
generally any raw value type that adopts Equatable. The consequence is that
my library can't allow its users to define an enum of table columns.
This is not a deal breaker. Everybody can live with this little caveat,
and I guess I'm the only one who wishes things had been more *consistent*.
But still: this story helps realizing that code synthesis can bite in
plenty of unexpected ways.
I don't understand what this has to do with synthesized Equatable.
Wouldn't manually implemented Equatable have the same impact? The design
of a DSL should be able to accommodate conformance to basic protocols
without ambiguity.
We generally want as many types to be Equatable and Hashable as possible.
Synthesized conformance means more types will have these conformance and
that's a good thing in all cases (so long as the implementation is
correct).
Thanks for reading,
Gwendal Roué
[1] GitHub - groue/GRDB.swift: A toolkit for SQLite databases, with a focus on application development
Le 7 sept. 2017 à 12:20, Haravikk via swift-evolution < > swift-evolution@swift.org> a écrit :
On 7 Sep 2017, at 00:11, Brent Royal-Gordon <brent@architechies.com> > wrote:
On Sep 5, 2017, at 1:02 PM, Haravikk via swift-evolution < > swift-evolution@swift.org> wrote:
This proposal idea is essentially for a new attribute @synthetic (name is
up for debate). This attribute is required for any default implementation
that includes reflective type compiler magic, use of the reflection API
against `self` or, in future, any native Swift macros within the method
(possibly limited to specific features, will depend on the macro language
and its capabilities).
"Use of the reflection API against `self`"? `String(describing:)` and
`String(reflecting:)` sometimes do that.
I see zero justification for having @synthetic cover all of these random
things, but not ordinary default implementations—they have the same amount
of dangerous implicitness.
Actually they don't; the problem here is that through reflection you are
accessing and manipulating concrete types. A non-reflective default
implementation *only* has access to what the protocol itself has defined.
The synthetic alternatives are instead diving into parts of a concrete type
that may have nothing to do with the protocol at all, and must therefore
make assumptions that cannot be guaranteed to be correct, this is what
makes them dangerous.
On 6 Sep 2017, at 23:43, Nevin Brackett-Rozinsky < > nevin.brackettrozinsky@gmail.com> wrote:
On Wed, Sep 6, 2017 at 5:42 PM, Haravikk via swift-evolution < > swift-evolution@swift.org> wrote:
the issue I'm trying to raise is that when those, and similar features,
are used in synthesised behaviour (default implementations based upon the
concrete type), that these behaviours should be opted into explicitly,
otherwise they open up potential for all kinds of bugs, even when the
assumptions being made about the concrete type are simple such as in the
case for Equatable/Hashable. There's just too much potential for this kind
of reflective protocol implementation to overreach; to me it feels very
much like going into a restaurant and the waiter coming across and
force-feeding me something I don't want instead of taking my order.
I might suggest that instead it is like you have gone into a pizza shop
and said, “I’d like a large veggie pizza please.” And they made you a pizza
with their standard dough and their standard sauce and their standard
cheese and their standard selection of vegetables.
Actually I don't think that's quite it either; to strain the analogy even
further, I'd say it's more like going into a pizza shop and saying "I'd
like a pizza" and the staff looking at you and deciding you look like a
vegetarian and giving you a vegetarian pizza.
The crux of the issue here are the assumptions that are being made; for a
standard default implementation there are no assumptions, because you're
operating on the basis of methods and properties that you yourself have
defined as the protocol creator that, where properly implemented, have
precisely defined requirements, behaviours etc. When you're doing it with
some form of compile-time or run-time reflection however you're messing
around with parts of a concrete type that the protocol itself doesn't
actually know anything about with any certainty.
It's the same reason that run-time reflection isn't something you should
ever want to do, because while it might work with all of the types you test
directly, if it supports arbitrary types (which it'd need to, or there'd be
no point to reflecting) then there's always the risk of encountering a type
where some part of it doesn't match the assumptions that you've made. Of
course there are use-cases where this may not matter, e.g- if you're just
dumping data from a type and don't really care if you're storing stuff that
isn't important, but in other cases such as Equatable and Hashable it
*can* make a difference, as it can drastically affect behaviour when
those assumptions fail.
On 7 Sep 2017, at 05:00, Andrew Thompson <mrwerdo331@me.com> wrote:
Perhaps we could find a solution by meeting in the middle. Introduce a
compiler flag that will disable the automatic synthesis and revert to the
old behaviour (i.e. swiftc main.swift —disable-automatic-synthesis )
Thanks for the suggestion, but that's a bit too all-or-nothing; I realise
I might seem very negative but to be clear, I *do* want these synthesised
features, I just don't want them in their current form. What I want is for
them to be explicitly opted into where I, as the developer, need them to
be, so that I'm in absolute, unambiguous control of when and where they are
used.
I do absolutely support features that eliminate tedious boiler-plate, I
just think that doing so implicitly in invasive ways is not the right way
to do it.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution