[pitch] CopyInitializable for value-type semantics

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.

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:

Immutable types like NSString, NSDictionary etc just return self for the copy.

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.

NSNumber is an immutable reference type, so copy just returns a strong reference to itself. So how would a copy initialization of a NSNumber add any value? If we were to add a copy initializer to NSNumber, it would probably be implemented as just replacing self in the init with the other object.

For reference types there is already a protocol for what you are attempting to do; NSCopying (granted it probably should have a Self return instead of Any… but that is a different can-o-worms).

···

On Jul 12, 2017, at 3:23 AM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> wrote:

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

Yes, NSNumber was a bad example. Immutable types can implement it and do a shallow copy, as you said.
The NSCopying protocol won’t do because:
It only works on @objc classes.
Its proper use is deprecated (namely, the use of provided NSZone parameter is deprecated).
It’s in Foundation framework, which has nothing to do with Swift, per se.

Besides, the Foundation framework is largely composed of outdated Objective-C code, which is a horrible fit for use in Swift.
If you ask me, Swift needs to phase out its use of Foundation types in favor of faster, more convenient and generally more swifty types.

···

On Jul 12, 2017, at 6:58 PM, Philippe Hausler <phausler@apple.com> wrote:

On Jul 12, 2017, at 3:23 AM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org <mailto: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

}

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:

Immutable types like NSString, NSDictionary etc just return self for the copy.

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.

NSNumber is an immutable reference type, so copy just returns a strong reference to itself. So how would a copy initialization of a NSNumber add any value? If we were to add a copy initializer to NSNumber, it would probably be implemented as just replacing self in the init with the other object.

For reference types there is already a protocol for what you are attempting to do; NSCopying (granted it probably should have a Self return instead of Any… but that is a different can-o-worms).

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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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"

···

on Wed Jul 12 2017, Gor Gyolchanyan <swift-evolution@swift.org> wrote:

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

I like the idea of Clonable, because it involves the least amount of boilerplate and I do certainly agree that not having to care about whether or not the object is a value type or a reference type is good for robust generic programming.
However, there are two problems that I see with it:
* The Clonable protocol is as pure magic as ExpressibleBy*Literal and Sequence, which may or may not be bad depending on the views and priorities of the core team.
* There is no way of avoiding a copy when one isn’t necessary. For instance, and object might be clonable, but that doesn’t mean that it should always be cloned.

The only good reason to use a value-type-semantical class is because if its deinit. And if you think about it, dragging an entire class with its virtual table is a gross overkill just for using ARC. Considering that classes and functions are the only reference types, things like Arrays and Dictionaries have to resort to gross hacks with using private classes and the ever so magical isKnowUniquelyReferenced function, which means that if you ever hope to allocate dynamic memory in a structure, you’re stuck with the Copy-On-Write semantic or a memory leak. The following magical protocol would solve all problems:

protocol ReferenceSemantics {
  
  func retain()

  func release()

}

This alone would make a huge variety of use cases easy to write and fast to run, including every single collection type in Swift’s standard library (except maybe CollectionOfOne). So, dynamic allocations in Swift currently are only possible through the gross overhead of classes or manual memory management.

Currently, the only way that I know of to dynamically allocate a fixed-size value type inside another value type with full ARC support and without using classes is to abuse the fact that escaped locals in functions are ARC-ed:

public struct Variable<Value> {

  public init(_ value: Value) {
    var value = value
    self.getter = { return value }
    self.setter = { value = $0 }
  }

  public var value: Value {
    get {
      return self.getter()
    }
    
    nonmutating set {
      self.setter(newValue)
    }
  }

  private let getter: () -> Value
  
  private let setter: (Value) -> Void

}

let v = Variable(“hello”) // a `let` constant
assert(v.value == “hello")
v = “world” // setter is nonmutating, so this is ok
assert(v.value == “world")

···

On Jul 25, 2017, at 9:21 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

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

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I like the idea of Clonable, because it involves the least amount of
boilerplate and I do certainly agree that not having to care about
whether or not the object is a value type or a reference type is good
for robust generic programming.

However, there are two problems that I see with it:

* The Clonable protocol is as pure magic as ExpressibleBy*Literal and
  Sequence, which may or may not be bad depending on the views and
  priorities of the core team.

Fortunately I am able to argue for that magic in person in core team
meetings :wink:

* There is no way of avoiding a copy when one isn’t necessary. For
instance, and object might be clonable, but that doesn’t mean that it
should always be cloned.

I mostly disagree. Remember that we're talking about generic code where
the type of the thing being cloned is not known and we are purely
accessing it through its protocol conformance. There are three cases:

1. The type is immutable, in which case it effectively has value
   semantics and cloning should be a no-op, so there's nothing to worry
   about.

2. The *type* is mutable but both participating *values* in context are
   only accessed with non-mutating operations of their non-class
   protocol requirements:

      func f<T : Clonable & Readable>(x: T) {
        let y = x // no need to clone here
        y.read()
        x.read()
      }

3. The rest of the cases: one of the participating values may be
   mutated:

      func g<T : Clonable & Readable & Writable>(x: T) {
        let y = x // clone here
        y.write()
        x.read()
      }

In this case, you simply must clone in order to have the function make
sense.

The only good reason to use a value-type-semantical class is because
if its deinit.

I disagree. Implementing CoW is a nontrivial bit of work. Even
creating an immutable forwarding wrapper struct is a nontrivial bit of
work. And furthermore, such classes are already part of the ecosystem
(I'm looking at you, NSNumber)!

And if you think about it, dragging an entire class with its virtual
table is a gross overkill just for using ARC.

I don't think I understand what you mean.

Considering that classes and functions are the only reference types,
things like Arrays and Dictionaries have to resort to gross hacks with
using private classes and the ever so magical isKnowUniquelyReferenced
function, which means that if you ever hope to allocate dynamic memory
in a structure, you’re stuck with the Copy-On-Write semantic or a
memory leak.

I don't think I understand. You can easily put a plain reference to a
class in a structure. That's a dynamic allocation that won't leak.

The following magical protocol would solve all problems:

protocol ReferenceSemantics {

  func retain()

  func release()

}

This alone would make a huge variety of use cases easy to write and
fast to run, including every single collection type in Swift’s
standard library (except maybe CollectionOfOne).

Sorry, I really can't envision how this protocol helps you.

So, dynamic allocations in Swift currently are only possible through
the gross overhead of classes or manual memory management.

AFAIK there's no serious overhead, beyond the cost of the allocation,
for creating a class instance.

One day, if Swift gets fully-realized ownership features, a noncopyable
struct could own dynamic memory without using ARC, but as long as the
ARC version isn't being copied either, the savings here would be
relatively small.

Currently, the only way that I know of to dynamically allocate a
fixed-size value type inside another value type with full ARC support
and without using classes is to abuse the fact that escaped locals in
functions are ARC-ed:

Have you got measurements that demonstrate that an ARC'd closure capture
context is less costly than a plain-old class?

(remember to make the class final ;->)

Coming back to the topic at hand... I really am lost. I *thought* we're
both trying to solve the same problem, which is that the semantics of
initialization and assignment in generic contexts are inconsistent
between classes and structs, and the language gives us no way to
compensate for that inconsistentency other than careful programming and
documentation. What does all this stuff about allocation have to do
with it?

Sorry for not following better; hope you can explain.

···

on Tue Jul 25 2017, Gor Gyolchanyan <gor.f.gyolchanyan-AT-icloud.com> wrote:

public struct Variable<Value> {

  public init(_ value: Value) {
    var value = value
    self.getter = { return value }
    self.setter = { value = $0 }
  }

  public var value: Value {
    get {
      return self.getter()
    }

    nonmutating set {
      self.setter(newValue)
    }
  }

  private let getter: () -> Value

  private let setter: (Value) -> Void

}

let v = Variable(“hello”) // a `let` constant
assert(v.value == “hello")
v = “world” // setter is nonmutating, so this is ok
assert(v.value == “world")

On Jul 25, 2017, at 9:21 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

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

<https://lists.swift.org/mailman/listinfo/swift-evolution>

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

<https://lists.swift.org/mailman/listinfo/swift-evolution>

--
-Dave

Terms of Service

Privacy Policy

Cookie Policy