[swift-evolution] Should we rename "class" when referring to protocol conformance?

Adrian Zubarev adrian.zubarev at devandartist.com
Sat May 7 03:52:16 CDT 2016


For me and my English its become hard to follow where this is going right now.

Let’s stick with my `AnyReference` and `AnyValue` protocols just for the example.

Do I get your intention right that we discuss here about value types that are constructed from their deepest only from other value types (same for `PureReferences`)? That’s what my understanding is for `PureValue`.

                   +-----+
                   | Any |
                   +--+--+
                      |
        +-------------+-------------+
        |                           |
 +------+-------+             +-----+----+
 | AnyReference |             | AnyValue |
 +--+---+-------+             +-----+----+
    |   |                           |
+-------+-------+             +-----+-----+ 
| PureReference |             | PureValue | 
+---------------+             +-----+-----+ 
    |
+---+--------------+ 
| AnyObject (ObjC) | 
+------------------+

Side note: If SE-0083 will be accepted it might be possible that @objc will be dropped from `AnyObject`, correct me if I'm wrong.

I'm fine with that if that’s the case, but I'm not if one-day Swift won't allow anymore to mix value types with reference types.

I also don't think that `PureReference` and `PureValue` protocols could be implicit, but their base protocols can.

Explicit usage of these protocols on actual types:

class A: PureValue {} // won't work

class B: PureReference {
	var value: OtherClass
} // can only contain only reference types (if that’s what we've been talking about here)

struct C: PureReference {} // won't work

struct D: PureValue {
	var value: Int
} // can only contain value types, and all value types inside follow the same rule

Explicit usage on protocols:

protocol E: SomeOtherProtocol, PureReference {} // shouldn't work, because if we want to replace the `class` keyword we should at least the rule that out constraint protocols should sit right behind `: ` or behind Any(Reference/Value) which will take the first place instead.

protocol F: PureReference {
	var value: SomeClass { get }
} // can only be applied to types that follow the pure reference constraint + SomeClass should follow the pure reference rule

protocol G: F {} // same as F

protocol H: SomeOtherProtocol, PureValue {} // same problem as with E

protocol I: PureValue {
	var value: Int
	func foo() -> Int
} // same rule as with class D

protocol J: PureValue {
	var value: SomeClass
} // should not work

protocol K: PureValue {
	func foo() -> SomeClass
} // I guess this should not work!?

protocol L: PureReference {
	func foo() -> Int
} // should this work?

protocol M: PureReference {
	func foo() -> SomeClass
} // like F

Implicit usage of the Any protocols:

class N {
	var value: SomeValue
	var anotherValue: SomeClass
	func foo() -> Int
	func boo() -> AClass
} // implicitly AnyReference; SomeClass can follow PureReference rule but still can be nested into a AnyClass or AnyValue type (same goes for SomeValue)

struct O {
	var value: SomeValue
	var anotherValue: SomeClass
	func foo() -> Int
	func boo() -> AClass
} // implicitly AnyValue; same rules as for N

Explicit protocol constrains:

protocol P: AnyValue, PureValue {} // or just PureValue

protocol Q: AnyReference, PureReference {} or just PureReference

protocol R: AnyValue {} // This protocol is problematic now, because logically we could apply it to a PureValue type but this shouldn’t work, just because we said that PureProtocols are more constrained

Now we have two options:

1. Split the Any and Pure concepts

                  +------+
                  | Pure |
                  +--+---+
                     |
       +-------------+-------------+
       |                           |
+------+--------+            +-----+-----+
| PureReference |            | PureValue |
+---------------+            +-----+-----+

                  +-----+
                  | Any |
                  +--+--+
                     |
       +-------------+------------+
       |                          |
+------+-------+            +-----+----+
| AnyReference |            | AnyValue |
+--------------+            +-----+----+

This solution feels strange. `Pure` is something like `AnyPure`.

2. Reorder Pure and Any protocols:

                  +-----+
                  | Any |
                  +--+--+
                     |
       +-------------+-------------+
       |                           |
+------+--------+            +-----+-----+
| PureReference |            | PureValue |
+------+--------+            +-----+-----+
       |                           |
+------+-------+             +-----+----+ 
| AnyReference |             | AnyValue | 
+--------------+             +-----+----+ 

protocol R: AnyValue {} // is fine now because we can't apply it to a PureValue anymore.

With this reorder I suggest that `PureValue` and `PureReference` also becomes implicit protocols, BUT all existing types should be implicitly `AnyReference` or `AnyValue` where the strict rule from `PureProtocols` is ignored/disabled. To use the constraints of pure protocols should require its explicit usage.

Does this make any sense to you or am I totally wrong here?

What I’d prefer could look like this:

                  +-----+
                  | Any |
                  +--+--+
                     |
       +-------------+------------+
       |                          |
+------+-------+            +-----+----+
| AnyReference |            | AnyValue |
+--------------+            +-----+----+

pure protocol A: AnyReference {}
pure class B: A {}

I'm not sure why this topic become so complicated where we only discussed about dropping the `class` keyword from protocols and I suggested to use implicit protocols instead, where we need at least one more for value types (I'm fine with `AnyObject` as long it won't bridge to ObjC types anymore, but I'd rename it for possible new reference type in the future of Swift).

-- 
Adrian Zubarev
Sent with Airmail

Am 7. Mai 2016 bei 09:56:24, Tyler Fleming Cloutier via swift-evolution (swift-evolution at swift.org) schrieb:


On May 5, 2016, at 8:02 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:


on Thu May 05 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote:

   On May 5, 2016, at 4:59 PM, Dave Abrahams <dabrahams at apple.com> wrote:

   on Wed May 04 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote:

       On May 4, 2016, at 5:50 PM, Dave Abrahams via swift-evolution
       <swift-evolution at swift.org> wrote:

       on Wed May 04 2016, Matthew Johnson <swift-evolution at swift.org> wrote:

       On May 4, 2016, at 1:29 PM, Dave Abrahams via swift-evolution
       <swift-evolution at swift.org> wrote:

       on Wed May 04 2016, Adrian Zubarev <swift-evolution at swift.org>
       wrote:

       Not sure what to think about the enum cases inside a
       protocol (if AnyEnum would
       even exist), it could be a nice addition to the language, but
       this is an own
       proposal I guess.

       We should start by adding AnyValue protocol to which all value
       types
       conforms.

       Having a way to constrain conformance to things with value semantics
       is
       something I've long wanted. *However*, the approach described is too
       simplistic. It's possible to build classes whose instances have
       value
       semantics (just make them immutable) and it's possible to build
       structs
       whose instances have reference semantics (just put the struct's
       storage
       in a mutable class instance that it holds as a property, and don't
       do
       copy-on-write). 

       In order for something like AnyValue to have meaning, we need to
       impose
       greater order. After thinking through many approaches over the
       years, I
       have arrived at the (admittedly rather drastic) opinion that the
       language should effectively outlaw the creation of structs and enums
       that don't have value semantics. (I have no problem with the idea
       that
       immutable classes that want to act as values should be wrapped in a
       struct). The language could then do lots of things much more
       intelligently, such as correctly generating implementations of
       equality.

       That is a drastic solution indeed! How would this impact things like
       Array<UIView>? While Array itself has value semantics, the aggregate
       obviously does not as it contains references which usually be mutated
       underneath us. 

       Value semantics and mutation can only be measured with respect to
       equality. The definition of == for all class types would be equivalent
       to ===. Problem solved.

       Similar considerations apply to simpler wrapper structs such as Weak.

       Same answer.

       Hmm. If those qualify as “value semantic” then what kind of structs and
       enums
       would not? A struct wrapping a mutable reference type certainly doesn’t
       “feel”
       value semantic to me and certainly doesn’t have the guarantees usually
       associated with value semantics (won’t mutate behind your back, thread
       safe,
       etc).

   Sure it does.

   public struct Wrap<T: AnyObject> : Equatable {
   init(_ x: T) { self.x = x }
   private x: T
   }

   func == <T>(lhs: Wrap<T>, rhs: Wrap<T>) -> Bool {
   return lhs.x === rhs.x
   }

   I defy you to find any scenario where Wrap<T> doesn't have value
   semantics, whether T is mutable or not.

   Alternately, you can look at the Array implementation. Array is a
   struct wrapping a mutable class. It has value semantics by virtue of
   CoW.

This goes back to where you draw the line as to the “boundary of the value”.
Wrap and Array are “value semantic” in a shallow sense and are capable of deep
value semantics when T is deeply value semantic. 

No, I'm sorry; this “deep-vs-shallow” thing is a fallacy that comes from
not understanding the boundaries of your value.  Or, put more
solicitously: sure, you can look at the world that way, but it just
makes everything prohibitively complicated, so why would you want to?

In my world, there's no such thing as a “deep copy” or a “shallow copy;”
there's just “copy,” which logically creates an independent version of
everything up to the boundaries of the value.  Likewise, there's no
“deep value semantics” or “shallow value semantics.”  Equality defines
value semantics, and the boundaries of an Array value always includes
the values of its elements.  The *only* problem here is that we have no
way to do equality comparison on some arrays because some types aren't
Equatable.  IMO the costs of not having everything be equatable, in
complexity-of-programming-model terms, are too high.

My point with this is, in case I was a bit rambling, if you’re going to draw boundaries around a value, for the purpose of copying or immutability, then equality should always match with those boundaries. As you say, “Equality defines value semantics, and the boundaries of an Array value always includes the values of its elements.”

Under this reasoning, custom equality violates these boundaries. And without custom equality, equality checks on reference types are essentially useless. But maybe value types in Swift are powerful enough where this doesn’t matter.



Both have their place, but the maximum benefit of value semantics
(purity) 

I don't know what definition of purity you're using.  The only one I
know of applies to functions and implies no side effects.  In that
world, there is no mutation and value semantics is equivalent to
reference semantics.

is derived from deep value semantics. This is when there is no
possibility of shared mutable state. This is an extremely important
property.

It's the wrong property, IMO.


let t = MyClass()
foo.acceptWrapped(Wrap(t))
t.mutate()

In this example, foo had better not depend on the wrapped instance not getting
mutated.

foo has no way to get at the wrapped instance, so it can't depend on
anything about it.

       My expectation is a generic aggregate such as Array would have to
       conditionally conform to AnyValue only when Element also conforms to
       AnyValue.

       I’m also wondering how such a rule would be implemented while still
       allowing for CoW structs that *do* implement value semantics, but do
       so while using references internally.

       I am not talking about any kind of statically-enforceable rule, although
       we could probably make warnings sophisticated enough to help with this.

       You said the you have arrived at the opinion that the language should
       “effectively outlaw” structs and enums that do not have value semantics.
       That
       sounded like static enforcement to me. 

   The language outlaws certain kinds of inout aliasing without
   providing static enforcement. This is like that.

I did not know this. Now you have me curious. Can you give an example of where
we are able to violate law? I ask mostly because it sounds like there is a
possibility of stumbling into dangerous territory, possibly without being aware
that you have done so.

See “In-out Parameters” in
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID362

       Maybe you meant we should allow the compiler to assume value semantics
       for structs and enums despite the fact that it doesn’t statically
       enforce this?

   That would be one *consequence* of effectively outlawing it. The library
   could make similar assumptions.

       If the compiler can be sophisticated enough to verify value semantics
       statically maybe it would be better to have that mechanism be
       triggered by conformance to AnyValue rather than for all structs and
       enums. Types that conform to AnyValue would receive the benefits of
       the compiler knowing they have value semantics, while other uses of
       structs and enums would remain valid. Best practice would be to
       conform structs and enums to AnyValue whenever possible.

       Another possible advantage of this approach would be allowing
       immutable reference types to conform to AnyValue and receive the
       associated benefits such as the generated implementation of equality,
       etc.

       -Matthew

       -- 
       Dave

       _______________________________________________
       swift-evolution mailing list
       swift-evolution at swift.org
       https://lists.swift.org/mailman/listinfo/swift-evolution

       _______________________________________________
       swift-evolution mailing list
       swift-evolution at swift.org
       https://lists.swift.org/mailman/listinfo/swift-evolution

       -- 
       Dave

       _______________________________________________
       swift-evolution mailing list
       swift-evolution at swift.org
       https://lists.swift.org/mailman/listinfo/swift-evolution

   -- 
   Dave


-- 
Dave
_______________________________________________
swift-evolution mailing list
swift-evolution at swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
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/20160507/48df98a5/attachment.html>


More information about the swift-evolution mailing list