[swift-evolution] [Pitch] Tweak `Self` and introduce `Current`

Xiaodi Wu xiaodi.wu at gmail.com
Sun Jan 8 11:15:09 CST 2017


On Sun, Jan 8, 2017 at 4:09 AM, Adrian Zubarev <
adrian.zubarev at 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 at swift.org) schrieb:
>
> On Sat, Jan 7, 2017 at 5:33 PM, Braeden Profile <jhaezhyr12 at 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170108/1d93fca2/attachment.html>


More information about the swift-evolution mailing list