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

Matthew Johnson matthew at anandabits.com
Mon May 23 12:51:37 CDT 2016


> On May 23, 2016, at 9:22 AM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> on Sun May 22 2016, Matthew Johnson <matthew-AT-anandabits.com <http://matthew-at-anandabits.com/>> wrote:
> 
>>> On May 22, 2016, at 3:42 PM, Dave Abrahams via swift-evolution
>>> <swift-evolution at swift.org> wrote:
>>> 
>>> 
>>> on Sun May 22 2016, Matthew Johnson <swift-evolution at swift.org> wrote:
>>> 
>>>> What I am arguing for is the ability to distinguish aggregates which
>>>> are logically isolated from aggregates which contain salient
>>>> references to shared mutable state. 
>>> 
>>> Everything with value semantics is logically isolated in that way.
>> 
>> Array<UIView> has salient references whose referent is shared mutable
>> state.  The references are salient attributes. You’re saying the
>> referent doesn’t matter because the boundary of the value stops at the
>> reference.  
> 
> I'm saying you can define things that way, and it helps to make the
> model for generic programming coherent.  Without something like this, it
> becomes almost impossible to specify the behavior of generic components
> in an understandable way without simultaneously preventing the use of
> reference values as simple object identities, which *are* values.  As I
> mentioned elsewhere, we could force users to wrap these reference values
> to produce something that doesn't have easily identifiable reference
> semantics, but the requirement of your `PureValue` that there should be
> *no way* to derive access to the referenced instance from the wrapped
> reference is too limiting.
> 
>> I’m saying it does matter in that it means the aggregate is no longer
>> logically isolated because shared mutable state is reachable through
>> the aggregate.  Therefore it is not isolated in that way that I was
>> intending to describe.
> 
> This, again, is a matter of your mental model.  I do think it's
> reasonable to say that—especially in Swift where reference-ness is often
> syntactically invisible—asking people to adopt a mental model that
> *directly* treats references as values is simply unrealistic.  In that
> case, using some kind of wrapper to represent object identity might be
> the only practical way to do this.

I think the lack of syntax is a good point.  I have wondered how this would play out in Swift.

However, I think it goes deeper than just being syntactically invisible.  If Swift used C-like pointer syntax that would give us a syntactic distinction but not a semantic one.

Consider your `Set<DrawableObject>` example.  In this case, maybe you really do just care about object identity so you can test set membership.  In this case you really are just viewing the references as values.  In this case you really do view `Set<DrawableObject>` as a pure value.

Consider another example - an `Order` with an `Array<LineItem>`.  In this case, you are looking at the array as a part of an aggregate.  You don’t care about the values of the references at all, except as a means to get you the object.  In this case `Array<LineItem>` isn’t a pure value (if `LineItem` is a type with reference semantics).

If we simply referred to all reference types with `*DrawableObject` syntax we would have to do that in both `Set<*DrawableObject>` and `Array<*LineItem>`.  The semantic distinction isn’t captured.

However, if we use an opaque wrapper type like you suggest we actually *can* capture the distinction.  We can say `Set<Identity<DrawableObject>>`.  It would be perfectly acceptable to me to view this as a pure value because it is clear that you are only looking at the value of the reference, not actually following the reference.  And if we really needed to include “unsafeDereference” or something like that for performance reasons I could probably live with that.  At least your intention is explicit.  If desired we could even introduce syntactic sugar for this wrapper like we have for `Optional`, `Array`, and `Dictionary` so you can talk about it more concisely.


> 
>>>> To be honest, I am really struggling to understand why this
>>>> distinction seems unimportant to you.
>>> 
>>> The non-exposure of shared mutable state is a hugely important
>>> property of well-encapsulated design.  However, I don't believe it's
>>> appropriate to represent that with a protocol, because I don't
>>> believe there exist any generic components whose correctness depends
>>> on it.
>> 
>> Do you believe it is appropriate to represent this in some other way
>> that allows us to state architectural intent and introduce constraints
>> for the purpose of structuring a large code base?
> 
> Sure, if it makes your life better, you should define it; I just don't
> see yet that it has any place in the standard library.  It is a
> principle of generic programming that protocols (concepts) are
> *discovered* through a specific process.  In the standard library, we
> don't define a new protocol until we have identified a family of
> concrete components that can be generalized based that protocol and
> whose correctness depends on the protocol's constraints.

I hope I am not coming across like I believe we should introduce this right now, or like I am attached to the specific mechanism of a PureValue protocol.  I am trying to advocate that there should be a long term goal in Swift that allows us to talk about types from which you cannot reach shared mutable state, hopefully have these types verified by the compiler, and use this as a generic constraint.  I could easily see the mechanism being at the language level: `pure struct`, `pure enum` and a `pure` protocol constraint similar to the `class` protocol constraint we have today (and of course `pure func` as well).  I know this is not going to happen in Swift 3 and probably not Swift 4, but I do think it is a very worthwhile goal.

Going on a tangent a bit.  I agree with the general goal of keeping the standard library very focused.  On the other hand, I do think this criteria for protocols is a bit to restrictive.  How do you define the criteria for whether a type gets into the standard library or not?  Why not just use that same criteria for protocols?  If it would be very useful to the Swift community to share a common protocol that is always available why would it be excluded just because no component of the standard library depends on that protocol for its correctness? 

A good example of this is the `Map` protocol that came up recently.  It is reasonably common to have code which only depends on the ability to read and / or write to a key value data structure and doesn’t need to be concerned with the underlying implementation.  It can be very useful to use a `Map` abstraction to hide the implementation.  The standard library includes types that could conform to this protocol.  It seems to me like a good idea to include a protocol like this in the standard library even if the implementation of its components does not depend on this protocol for correctness.

> 
> HTH,
> 
> -- 
> -Dave

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160523/c0df99b6/attachment.html>


More information about the swift-evolution mailing list