[swift-evolution] [pitch] CopyInitializable for value-type semantics

Dave Abrahams dabrahams at apple.com
Tue Jul 25 11:07:55 CDT 2017


on Wed Jul 12 2017, Gor Gyolchanyan <swift-evolution at 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>

-- 
-Dave



More information about the swift-evolution mailing list