On Sun, Jan 8, 2017 at 4:09 AM, Adrian Zubarev < adrian.zuba...@devandartist.com> wrote:
> There are a few good points made here. It’s an interesting workaround to > use type(of:) to get a similar behavior I wanted. My point is, that > Current or call it static Self should exist to fill some gaps in our > design patterns. Using Self in protocols should not always mean that you > shall use the dynamic behavior. Yes on value types the dynamic type is the > same conforming type. > I'm not sure in what way you intend to distinguish between static and dynamic Self in protocols. - If class `Base` conforms to protocol `P`, then final class `Derived : Base` also conforms to protocol `P`. This is, AFAIK, non-negotiable. - In Swift, a type can only conform to a protocol in one way. This is unlikely to change in Swift 4 or 5. - If `P` guarantees a method called `frobnicate()` and we have `let foo = Derived()`, then `foo.frobnicate()` and `(foo as Base).frobnicate()` invoke the same method. See: < https://www.raizlabs.com/dev/2016/12/swift-method-dispatch/> about dispatch rules. - If `frobnicate()` is guaranteed by `P` to return a value of type `Self`, `Base.frobnicate()` must provide an implementation that returns a value of type `Self` and `Derived.frobnicate()` must provide an implementation that returns a value of type `Derived`. Both `foo.frobnicate()` and `(foo as Base).frobnicate()` invoke the implementation on `Derived`, which returns a value of type `Derived`. - As Anton wrote, one improvement is to make it possible to write `Base.frobnicate()` by spelling out the return value as `Base` instead of `Self`, with the consequence that `Derived` _must_ also provide its own implementation that spells out `Derived`. However, unless I'm mistaken, since Swift allows overloading by return type, `Derived.frobnicate()` would be an overload and not an override of `Base.frobnicate()`, and yet `(foo as Base).frobnicate()` must dispatch to the implementation on `Derived`. - If `frobnicate()` could be guaranteed to return a value of type `StaticSelf`, `Base.frobnicate()` must provide an implementation that returns a value of type `Base` and `Derived.frobnicate()` must still provide an implementation that returns a value of type `Derived`. Both `foo.frobnicate()` and `(foo as Base).frobnicate()` must still invoke the implementation on `Derived`, which still returns a value of type `Derived`. - If Anton's suggestion is adopted and the issues with dispatch overcome, then the above would be possible, but both the `StaticSelf` and `Self` versions appear to do the same thing. If Anton's suggestion is not adopted, then the above would not be possible. So, why do you think you need `StaticSelf` in the context of protocols? // All these protocol do not compile > // This is not real world example, but IMO should be possible > protocol P : class { > > associatedtype StaticSelf : AnyObject > func castOrTrap<T : StaticSelf>() -> T > } > > protocol P : class { > > associatedtype StaticSelf : Self > func castOrTrap<T : StaticSelf>() -> T > } > > protocol P : class { > > func castOrTrap<T : Self>() -> T > } > > The absence of static Self does hurt the flexibility of the language to > design a specific but yet clear behavior. The P protocol is meant to have > a static Self, where conforming to it would result in a shorthand version > using StaticSelf/Current or the conforming type: > > class A : P { > > func castOrTrap<T : Current>() -> T { … } > // or > func castOrTrap<T : A>() -> T { … } > } > > I cannot think of a possible workaround here except of defining that > method on the base type itself. If other not derived classes need to have > this pattern, I cannot create an ancestor protocol as I might would like. > ------------------------------ > > The forced required init might be a good workaround for the issue from > some previous post, but is it really what we always wanted? This > restriction lives only on the non-final classes because Self there is > dynamic, which feels kinda inconsistent. As I already mentioned, I’d be > happy if we could drop the restriction on the conforming type to allow the > user to decide if we want to follow the contract of using self > (lowercased) and type(of: self) or could simply override Self with > ContainingTypeName. That would solve that issue, if I’m not totally > missing here something. > > But the issue from above remans unsolved. Furthermore, does dynamic Self > make any sense on non-final classes as a parameter type? Can anyone show me > a plausible code snippet for that? > ------------------------------ > > Sure my arguments are more like *might*, *want* etc. and not that much of > a weight, but that’s my honest opinion that some restrictions makes the > language less flexible as it could be. > > Self is exactly like .Type which might also be magically .Protocol. The > static and dynamic behaviors are baked into one place. :/ > > > > -- > Adrian Zubarev > Sent with Airmail > > Am 8. Januar 2017 um 00:51:32, Xiaodi Wu via swift-evolution ( > swift-evolution@swift.org) schrieb: > > On Sat, Jan 7, 2017 at 5:33 PM, Braeden Profile <jhaezhy...@gmail.com> > wrote: > >> >>> Of course, I would love being able to use an initializer setup, but >>> there are serious bugs in the implementation. >>> >>> protocol Clonable >>> { >>> init(other: Self) >>> } >>> >>> extension Clonable >>> { >>> func clone() -> Self >>> { return type(of: self).init(other: self) } >>> } >>> >>> >>> class Base: Clonable >>> { >>> var x: Int >>> >>> init(x: Int) >>> { self.x = x } >>> >>> required init(other: Base) >>> { self.x = other.x } >>> } >>> >>> class Derived: Base >>> { >>> var y: String >>> >>> init(x: Int, y: String) >>> { >>> self.y = y >>> super.init(x: x) >>> } >>> >>> // Should be required by the Clonable protocol, but it isn't. >>> required init(other: Derived) >>> { >>> self.y = other.y >>> super.init(other: other) >>> } >>> >>> // Required because it was `required` in Base. Even a `Derived` calls >>> this initializer to clone, which is wrong. Bugs abound. >>> required init(other: Base) >>> { fatalError("init(other:) is wrong.") } >>> } >>> >>> >>> >>> let me = Derived(x: 1, y: "food") >>> let alienClone = me.clone() // "init(other:) is wrong." >>> >>> >> Agree. That seems wrong. Great example. >> >> >> So, is this odd behavior intentional, a bug, or a design deficiency? I >> would think that when a protocol has a method or initializer has `Self` >> parameters—like in Clonable—every subclass would be required to implement >> its own specialized version (much like a required initializer). That would >> be a special case of the protocol system, though. >> >> As it sits, even fixing the calling behavior of my example leaves us with >> the problem of subclasses inheriting inapplicable required initializers >> from superclasses that actually don’t make any sense. >> >> Does this deserve its own thread? >> > > Dunno, maybe best to have its own thread. It's not mentioned as part of > SE-0068, but IMO a complete design that respects the spirit of that > proposal *should* involve allowing you to write: > > ``` > class Base : Clonable { > required init(other: Self) { ... } > } > > class Derived : Base { > required init(other: Self) { ... } > } > ``` > > > _______________________________________________ > 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