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

}

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. 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. 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.

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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to