[swift-evolution] Class mutation model and value-constrained protocols

Jonathan Hull jhull at gbis.com
Tue Jul 5 16:34:06 CDT 2016


Comments inline.


> On Jul 5, 2016, at 2:11 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> on Tue Jul 05 2016, Jonathan Hull <jhull-AT-gbis.com <http://jhull-at-gbis.com/>> wrote:
> 
>> Comments inline
>>> A “mutating” keyword on a protocol method is only useful if
>>> you can reason about the mutation in generic code.  
>>> 
>>>  protocol P {
>>>    mutating func changeMe()
>>>    func noChange() -> Int
>>>  }
>>> 
>>> In other words, given x of some type conforming to P, it should be
>>> meaningful that this is an error:
>>> 
>>>  func f<T: P>(x: T) {
>>>    immutable.changeMe() // can't mutate a "let" binding
>>>  }
>> 
>> Agreed.
>> 
>>> which means that you shouldn't be able to get around that meaning by
>>> writing:
>>> 
>>>  func g<T: P>(x: T) {
>>>    var mutable = x
>>>    mutable.changeMe() // OK
>>>  }
>> Now I am confused.  Why should this be illegal?  Doesn’t this work with structs right now? (you are working on a mutable copy)
> 
> What I meant by “you shouldn't be able get around that” was that the
> code shouldn't change the value of x.

Ah.  Gotcha.

In that case, my same comments apply re: general issues of confusion around reference types.  If x is a reference type, then you should even be able to call changeMe() without creating a mutable copy, no?

It sounds like what you are really wanting here is some notion of purity. All of the same arguments from above could be made about side effects in general (of which mutation is one example).


>>> Also, you should be able to reason that both of the following print the same
>>> thing twice, for types whose methods have no external side-effects:
>>> 
>>>  func gg<T: P>(x: T) {
>>>    print(x)
>>>    x.noChange()
>>>    print(x)
>>>  }
>>> 
>>>  func ggg<T: P>(x: T) {
>>>    print(x)
>>>    var y = x
>>>    y.changeMe()
>>>    print(x)
>>>  }
>> I see the issue here.  Though, it seems to me it is just a more easily
>> discoverable version of an entire class of problem with reference
>> types in general. X could be changed on a different thread too, for
>> example.  or in a nested function call.
>> 
>> You would also still have the same problem with 'let y = x'.
>> 
>>> When T is a class type, it can easily violate *all* of these
>>> expectations.  In other words, classes naturally bypass the mutation
>>> model.
>>> 
>>> If we are going to maintain source stability after Swift 3, it seems
>>> that we either need to address this now, or decide that it won't be
>>> addressed, because of the “viral const” problem.
>>> 
>>> One idea that Jordan and I have floated is that protocols with mutating
>>> methods should be constrained to applying to non-class types.  That
>>> would be a step in the right direction, but, that still leaves cases
>>> like gg able to easily violate expectations when the protocol in
>>> question has no mutating methods.
>> 
>> I really *dislike* the approach of disallowing class types for
>> protocols with mutating methods, unless an additional reference type
>> is added.  I have several protocols which have conforming classes and
>> structs and that that lets you choose reference vs value semantics.
> 
> How do you write generic code that works on both reference and value
> types conforming to such a protocol?

I don’t make those assumptions around mutability.  i.e. I know it *could* be a reference type and account for those cases.

I do like the idea of having a way to specify that a protocol can only apply to a value type for the cases where those guarantees around mutability are needed.


>> I would much rather have us mark class methods as mutating when they
>> change the class’s value, and just having the concept be separate from
>> let/var in that case.
>> 
>>> Another possibility would be to formalize the idea of value semantics in
>>> protocol declarations, so that non-class protocols were only allowed to
>>> apply to values.
>> 
>> I would like to have a way to require value semantics in a protocol
>> (similar to how we can require ‘class' now).  I still really want/need
>> the ability to have a protocol which can be adhered to by both value
>> and class types though...
> 
> I really want to see examples of generic code that isn't terribly tricky
> to write correctly so it will work on both kinds of type.
> 
>>> It's also possible that I've overestimated the seriousness of the issue
>>> and we actually can afford to postpone thinking about it until after
>>> Swift 4.
>>> 
>>> Thoughts?
>> 
>> I would vote to postpone to swift 4, and have it be part of a larger
>> discussion involving the marking of side-effects and thread safe
>> mutability.
>> 
>> If you need a stop-gap for Swift 3, I would be in favor of adding the
>> ability to mark a particular protocol as needing to be a value type
>> (not every protocol… just those that are marked as such).  That should
>> give you the guarantees you need for particular projects.
> 
> That's a reasonable stopping point.
> 
> -- 
> Dave

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


More information about the swift-evolution mailing list