on Wed Jul 12 2017, Gor Gyolchanyan <swift-evolution@swift.org> wrote:
> Hello, swift community! > > Recently I’ve come across a dilemma regarding value-type semantics > when dealing with generic types. Consider a protocol that has a > mutating in-place function and a non-mutating returning variant of > that function: > > protocol Transmogrifier { > > mutating func transmogrify() > > func transmogrified() -> Self > > } Ah, we're back in the territory of https://github.com/apple/swift/blob/master/docs/proposals/ValueSemantics.rst https://github.com/apple/swift/blob/master/docs/proposals/Inplace.rst (note, these are old; the syntax of the language has since evolved). > One of these methods has to have a default implementation in terms of the > other. > > One way doing it is to implement the mutating version in terms of > non-mutating because it doesn’t depend on additional conditions to > work, since assigning to `self` causes a complete copy of the internal > state of the object regardless of whether it’s a value type or a > reference type. However, this approach has a big downside: in many > cases mutating functions mutate only part of the instance, which means > that an efficient implementation will have to implement the mutating > version and because of the way the default implementation works, the > non-mutating version would also need to be manually implemented, which > makes the default implementation useless in those cases. > > Implementing the non-mutating version in terms of mutating version > solves this problem nicely, allowing one to focus on mutating only the > necessary parts of the instance, while leaving the need to return a > separate instance to the default implementation, which would be > perfectly adequate in most cases. Yes. There are cases where this approach ends up being less efficient, e.g. when one of the arguments to the non-mutating form has uniquely-referenced dynamically allocated storage that can be used by the result, but handling these cases efficiently requires special handling anyway, so the default implementation arrangement you're suggesting makes sense. > This approach has its own problem that this pitch seeks to solve. The > problem becomes apparent when you consider this naive implementation: > > extension Transmogrifier { > > public func transmogrified() -> Self { > var result = self > result.transmogrify() > return result > } > > } > > The above implementation is only correct for value types, because > assignment is a deep copy. Yes, I've wanted a way to constrain a generic so it only works on values since before the release of Swift 1. There are at least two issues that are still unsettled: 1. what about value types that don't have value semantics? 2. what about immutable final class types (that do have value semantics)? > If the instance is of a reference type, the assignment will do nothing > and the call to the mutating version will apply to the original > object, violating the postcondition of the function (which states that > the function shall not modify the instance in any way). > > The most straight-forward way of solving this problem is to introduce > a new protocol for making sure the original instance is always copied: > > protocol CopyInitializable { > > init(copying other: Self) > > } > > In which case the default implementation becomes fully correct: > > // The `CopyInitializable` conformance can also be moved to the protocol > itself > // if the protocol conformance requires value-type semantics. > extension Transmogrifier where Self: CopyInitializable { > > public func transmogrified() -> Self { > var result = Self(copying: self) > result.transmogrify() > return result > } > > } > > The downside of this approach is the need to manage CopyInitializable > conformance of the types that becomes extra hassle that seems to > conflict with the behavior of value types. It also, in generic code, undermines one of the great strengths of value types: that we don't have to worry about defensively copying them just to avoid unwanted side-effects. I know it implies a fair bit of magic, but I suggest we consider a system where you could write extension Transmogrify where Self : Clonable { public func transmogrified() -> Self { var result = self // <======== result.transmogrify() return result } } and in this context, a class that conformed to Clonable would be implicitly cloned on the specified line. An immutable final class could implement clone() as "return self" > This pitch proposes adding CopyInitializable protocol to the swift standard > library and having the > compiler automatically generate conformance to it for all value types. > This would immediately solve all problems of correct convenient > implementations of non-mutaiting > variants of in-place functions as well as remove the hassle of having to > manage conformance to > CopyInitializable for all value types that are guaranteed to have this > behavior in the first place. > > An good use case would be the NSNumber class, which would conform to > CopyInitializable and make use > of a single obvious mutating-to-nonmutating implementation of arithmetic > operations that would work > equally well on all standard numeric types. > > I’d like to hear opinions regarding this pitch and in case of consensus, I’d > write an official > proposal and offer it for review. > > Regards, > Gor Gyolchanyan. > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution > -- -Dave _______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution