struct subtyping


(Tino) #1

Many languages which adopt the concept of value types don't allow subclassing for those, and so does Swift.
Inheritance for structs is more complex than inheritance for classes, but the "final" limitation isn't the only possible solution, and Dave Abrahams told me in another thread that changing this rule might be considered in the future — so I'll risk getting taunted by the cool kids who are in favor of eliminating all ancient OOP-ideas :wink: and start a discussion.

I guess most readers know about the low-level problems that arise when we switch from pointers (always the same size) to value types (size may vary), so I'll start with two possibilities for struct subtyping:

newtype (see https://www.haskell.org/tutorial/moretypes.html — or just read on if you are scared by Haskell :wink:

When a subtype does not add any stored properties to its superclass (memory layout doesn't change), there is no difference at the level of object code — only the type checker may stop you from using those two types interchangeably.
Some use cases:
- In Cocoa, there is no separate class for (file system) paths; instead, there are some additions to NSString. String doesn't have those abilities, and imho methods like "stringByAppendingPathExtension" deserve a separate Path-struct, so that those special methods don't pollute the method list of String (URL is the future, so that example is somewhat out-of date).
- You could impose incompatibility on numeric types to ensure that your calculations use correct quantities. Although this can be annoying (Float vs. CGFloat), decorating numbers with quantity/unit could eliminate bugs that had really disastrous consequences in the past.
- Increased comfort for floating-point math:
struct CustomDouble: Double

func == (a: CustomDouble, b: CustomDouble) -> Bool {
  return abs(a.value - b.value) < 0.01
}
(no need to specify tolerance for each comparison)

Full subtyping

As long as you don't cross module borders, it wouldn't be that complicated to add inheritance without restrictions.
imagine you have a "Customer"-type and a "Employee"-type to store personal data (name, address…).
Those data objects are perfect candidates to be implemented as structs, but they also cry for a "Person"-superclass, so you are forced to either duplicate code, or to implement your objects as reference types.

In a real proposal, I would include more details on the problems caused by this feature, but I'd like to see some feedback first.

Best regards,
Tino


(Jim Kubicek) #2

I’m a big fan of adding `newtype` behavior to swift, I think being able to add new types that mirror (but are not compatible with) existing types would be powerful and I would get a lot of use out of them. I’m not a fan of adding subtyping to structs, IMHO, the complexity is not worth the power.

Could we accomplish something similar to `newtype` by making the `typealias` declaration more powerful? If we had the ability to extend typealiases it would get us most of the way there.

···

On Mar 21, 2016, at 4:58 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Many languages which adopt the concept of value types don't allow subclassing for those, and so does Swift.
Inheritance for structs is more complex than inheritance for classes, but the "final" limitation isn't the only possible solution, and Dave Abrahams told me in another thread that changing this rule might be considered in the future — so I'll risk getting taunted by the cool kids who are in favor of eliminating all ancient OOP-ideas :wink: and start a discussion.

I guess most readers know about the low-level problems that arise when we switch from pointers (always the same size) to value types (size may vary), so I'll start with two possibilities for struct subtyping:

newtype (see https://www.haskell.org/tutorial/moretypes.html — or just read on if you are scared by Haskell :wink:

When a subtype does not add any stored properties to its superclass (memory layout doesn't change), there is no difference at the level of object code — only the type checker may stop you from using those two types interchangeably.
Some use cases:
- In Cocoa, there is no separate class for (file system) paths; instead, there are some additions to NSString. String doesn't have those abilities, and imho methods like "stringByAppendingPathExtension" deserve a separate Path-struct, so that those special methods don't pollute the method list of String (URL is the future, so that example is somewhat out-of date).
- You could impose incompatibility on numeric types to ensure that your calculations use correct quantities. Although this can be annoying (Float vs. CGFloat), decorating numbers with quantity/unit could eliminate bugs that had really disastrous consequences in the past.
- Increased comfort for floating-point math:
struct CustomDouble: Double

func == (a: CustomDouble, b: CustomDouble) -> Bool {
  return abs(a.value - b.value) < 0.01
}
(no need to specify tolerance for each comparison)

Full subtyping

As long as you don't cross module borders, it wouldn't be that complicated to add inheritance without restrictions.
imagine you have a "Customer"-type and a "Employee"-type to store personal data (name, address…).
Those data objects are perfect candidates to be implemented as structs, but they also cry for a "Person"-superclass, so you are forced to either duplicate code, or to implement your objects as reference types.

In a real proposal, I would include more details on the problems caused by this feature, but I'd like to see some feedback first.

Best regards,
Tino
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Sweeris) #3

+1

···

On Mar 21, 2016, at 6:58 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Many languages which adopt the concept of value types don't allow subclassing for those, and so does Swift.
Inheritance for structs is more complex than inheritance for classes, but the "final" limitation isn't the only possible solution, and Dave Abrahams told me in another thread that changing this rule might be considered in the future — so I'll risk getting taunted by the cool kids who are in favor of eliminating all ancient OOP-ideas :wink: and start a discussion.

I guess most readers know about the low-level problems that arise when we switch from pointers (always the same size) to value types (size may vary), so I'll start with two possibilities for struct subtyping:

newtype (see https://www.haskell.org/tutorial/moretypes.html — or just read on if you are scared by Haskell :wink:

When a subtype does not add any stored properties to its superclass (memory layout doesn't change), there is no difference at the level of object code — only the type checker may stop you from using those two types interchangeably.
Some use cases:
- In Cocoa, there is no separate class for (file system) paths; instead, there are some additions to NSString. String doesn't have those abilities, and imho methods like "stringByAppendingPathExtension" deserve a separate Path-struct, so that those special methods don't pollute the method list of String (URL is the future, so that example is somewhat out-of date).
- You could impose incompatibility on numeric types to ensure that your calculations use correct quantities. Although this can be annoying (Float vs. CGFloat), decorating numbers with quantity/unit could eliminate bugs that had really disastrous consequences in the past.
- Increased comfort for floating-point math:
struct CustomDouble: Double

func == (a: CustomDouble, b: CustomDouble) -> Bool {
  return abs(a.value - b.value) < 0.01
}
(no need to specify tolerance for each comparison)

Full subtyping

As long as you don't cross module borders, it wouldn't be that complicated to add inheritance without restrictions.
imagine you have a "Customer"-type and a "Employee"-type to store personal data (name, address…).
Those data objects are perfect candidates to be implemented as structs, but they also cry for a "Person"-superclass, so you are forced to either duplicate code, or to implement your objects as reference types.

In a real proposal, I would include more details on the problems caused by this feature, but I'd like to see some feedback first.

Best regards,
Tino
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Howard Lovatt) #4

+1

···

On Monday, 21 March 2016, Tino Heth via swift-evolution < swift-evolution@swift.org> wrote:

Many languages which adopt the concept of value types don't allow
subclassing for those, and so does Swift.
Inheritance for structs is more complex than inheritance for classes, but
the "final" limitation isn't the only possible solution, and Dave Abrahams
told me in another thread that changing this rule might be considered in
the future — so I'll risk getting taunted by the cool kids who are in favor
of eliminating all ancient OOP-ideas :wink: and start a discussion.

I guess most readers know about the low-level problems that arise when we
switch from pointers (always the same size) to value types (size may vary),
so I'll start with two possibilities for struct subtyping:

*newtype* (see https://www.haskell.org/tutorial/moretypes.html — or just
read on if you are scared by Haskell :wink:

When a subtype does not add any stored properties to its superclass
(memory layout doesn't change), there is no difference at the level of
object code — only the type checker may stop you from using those two types
interchangeably.
Some use cases:
- In Cocoa, there is no separate class for (file system) paths; instead,
there are some additions to NSString. String doesn't have those abilities,
and imho methods like "stringByAppendingPathExtension" deserve a separate
Path-struct, so that those special methods don't pollute the method list of
String (URL is the future, so that example is somewhat out-of date).
- You could impose incompatibility on numeric types to ensure that your
calculations use correct quantities. Although this can be annoying (Float
vs. CGFloat), decorating numbers with quantity/unit could eliminate bugs
that had really disastrous consequences in the past.
- Increased comfort for floating-point math:
struct CustomDouble: Double

func == (a: CustomDouble, b: CustomDouble) -> Bool {
return abs(a.value - b.value) < 0.01
}
(no need to specify tolerance for each comparison)

*Full subtyping*

As long as you don't cross module borders, it wouldn't be that complicated
to add inheritance without restrictions.
imagine you have a "Customer"-type and a "Employee"-type to store personal
data (name, address…).
Those data objects are perfect candidates to be implemented as structs,
but they also cry for a "Person"-superclass, so you are forced to either
duplicate code, or to implement your objects as reference types.

In a real proposal, I would include more details on the problems caused by
this feature, but I'd like to see some feedback first.

Best regards,
Tino

--
-- Howard.


(Andrey Tarantsov) #5

+1, would love this.

A.


(Colin Barrett) #6

Many languages which adopt the concept of value types don't allow subclassing for those, and so does Swift.
Inheritance for structs is more complex than inheritance for classes, but the "final" limitation isn't the only possible solution, and Dave Abrahams told me in another thread that changing this rule might be considered in the future — so I'll risk getting taunted by the cool kids who are in favor of eliminating all ancient OOP-ideas :wink: and start a discussion.

I guess most readers know about the low-level problems that arise when we switch from pointers (always the same size) to value types (size may vary), so I'll start with two possibilities for struct subtyping:

newtype (see https://www.haskell.org/tutorial/moretypes.html — or just read on if you are scared by Haskell :wink:

When a subtype does not add any stored properties to its superclass (memory layout doesn't change), there is no difference at the level of object code — only the type checker may stop you from using those two types interchangeably.

As I understand it, a single argument struct ends up being as “free” as a newtype is in Haskell (modulo resiliency concerns), or close to it. For instance, my understanding is that the Int type is a single argument struct wrapper around a lower-level numeric type.

Some use cases:
- In Cocoa, there is no separate class for (file system) paths; instead, there are some additions to NSString. String doesn't have those abilities, and imho methods like "stringByAppendingPathExtension" deserve a separate Path-struct, so that those special methods don't pollute the method list of String (URL is the future, so that example is somewhat out-of date).
- You could impose incompatibility on numeric types to ensure that your calculations use correct quantities. Although this can be annoying (Float vs. CGFloat), decorating numbers with quantity/unit could eliminate bugs that had really disastrous consequences in the past.
- Increased comfort for floating-point math:
struct CustomDouble: Double

func == (a: CustomDouble, b: CustomDouble) -> Bool {
  return abs(a.value - b.value) < 0.01
}
(no need to specify tolerance for each comparison)

Full subtyping

As long as you don't cross module borders, it wouldn't be that complicated to add inheritance without restrictions.
imagine you have a "Customer"-type and a "Employee"-type to store personal data (name, address…).
Those data objects are perfect candidates to be implemented as structs, but they also cry for a "Person"-superclass, so you are forced to either duplicate code, or to implement your objects as reference types.

There’s a number of wrinkles that are worth considering—for instance, are you doing nominal or structural subtyping? The value-nature of structs suggests that structural subtyping would be useul. However, structs are already nominal types (unlike tuples).

Some compelling use cases for why a class doesn’t suffice and you really need a struct would enhance a full proposal.

Cheers,
-Colin

···

On Mar 21, 2016, at 7:58 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:


(Haravikk) #7

I’m a +1 for this. It would have been ideal for something I coded recently where I ended up having to settle for a class hierarchy instead so I could add incrementally without polluting the parent types with a ton of extra methods. I didn’t actually need the polymorphism, so structs would have been preferable, but just not as practical, but with sub-typing I could have had the best of both that I really wanted.

···

On 21 Mar 2016, at 11:58, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Many languages which adopt the concept of value types don't allow subclassing for those, and so does Swift.
Inheritance for structs is more complex than inheritance for classes, but the "final" limitation isn't the only possible solution, and Dave Abrahams told me in another thread that changing this rule might be considered in the future — so I'll risk getting taunted by the cool kids who are in favor of eliminating all ancient OOP-ideas :wink: and start a discussion.

I guess most readers know about the low-level problems that arise when we switch from pointers (always the same size) to value types (size may vary), so I'll start with two possibilities for struct subtyping:

newtype (see https://www.haskell.org/tutorial/moretypes.html — or just read on if you are scared by Haskell :wink:

When a subtype does not add any stored properties to its superclass (memory layout doesn't change), there is no difference at the level of object code — only the type checker may stop you from using those two types interchangeably.
Some use cases:
- In Cocoa, there is no separate class for (file system) paths; instead, there are some additions to NSString. String doesn't have those abilities, and imho methods like "stringByAppendingPathExtension" deserve a separate Path-struct, so that those special methods don't pollute the method list of String (URL is the future, so that example is somewhat out-of date).
- You could impose incompatibility on numeric types to ensure that your calculations use correct quantities. Although this can be annoying (Float vs. CGFloat), decorating numbers with quantity/unit could eliminate bugs that had really disastrous consequences in the past.
- Increased comfort for floating-point math:
struct CustomDouble: Double

func == (a: CustomDouble, b: CustomDouble) -> Bool {
  return abs(a.value - b.value) < 0.01
}
(no need to specify tolerance for each comparison)

Full subtyping

As long as you don't cross module borders, it wouldn't be that complicated to add inheritance without restrictions.
imagine you have a "Customer"-type and a "Employee"-type to store personal data (name, address…).
Those data objects are perfect candidates to be implemented as structs, but they also cry for a "Person"-superclass, so you are forced to either duplicate code, or to implement your objects as reference types.

In a real proposal, I would include more details on the problems caused by this feature, but I'd like to see some feedback first.

Best regards,
Tino
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrew Bennett) #8

+1 this would be great, particularly if:
* subtypes can be extended
* subtypes can be generic partial specialisations (
https://github.com/apple/swift-evolution/blob/master/proposals/0048-generic-typealias.md
)

···

On Thursday, 24 March 2016, Andrey Tarantsov via swift-evolution < swift-evolution@swift.org <javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>> wrote:

+1, would love this.

A.

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


(Tino) #9

Could we accomplish something similar to `newtype` by making the `typealias` declaration more powerful?

It's the first thing I tried, but afair someone from the Core Team opposed extending typealias - and I had to agree that an "alias" should be just a synonym.


(James Campbell) #10

Would love a way of extending an existing struct as a new type but it not
being related to the struct it extends.

Sort of like a mixin but for structs, so I could have:

struct Object {
let identifier: String
}

struct User: Object {

}

struct Tree: Object {
}

The last two structs get the identifier property "mixed" in but are unique
types :slight_smile:

···

*___________________________________*

*James⎥Head Of CEO*

*james@supmenow.com <james@supmenow.com>⎥supmenow.com <http://supmenow.com>*

*Sup*

*Runway East *

*10 Finsbury Square*

*London*

* EC2A 1AF *

On Thu, Mar 24, 2016 at 2:16 PM, Tino Heth via swift-evolution < swift-evolution@swift.org> wrote:

> Could we accomplish something similar to `newtype` by making the
`typealias` declaration more powerful?
It's the first thing I tried, but afair someone from the Core Team opposed
extending typealias - and I had to agree that an "alias" should be just a
synonym.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tino) #11

struct Object {
let identifier: String
}

struct User: Object {

}

struct Tree: Object {
}

The last two structs get the identifier property "mixed" in but are unique types :slight_smile:

Is unique meant so that User-Objects can't be used as "Object"-Object parameters?
I'm asking because this might be the biggest source of confusion with struct inheritance:
As the two "child-structs" don't add new data to their parent, they would (technically) be compatible — but this is fragile, so it might be preferable to "hide" polymorphism by default, and maybe add an annotation to explicitly allow that a sub-struct can be used as its parent type.
@compatible(Float) struct SpecialFloat: Float...


(James Campbell) #12

I was treating this as a Mixin.

So the property from Object is Mixed into Tree and User but it isn't a
Subclass so any comparison between the two isn't possible.

But on reflection you bring up a very valid point.

···

*___________________________________*

*James⎥Head Of CEO*

*james@supmenow.com <james@supmenow.com>⎥supmenow.com <http://supmenow.com>*

*Sup*

*Runway East *

*10 Finsbury Square*

*London*

* EC2A 1AF *

On Thu, Mar 24, 2016 at 5:34 PM, Tino Heth <2th@gmx.de> wrote:

struct Object {
let identifier: String
}

struct User: Object {

}

struct Tree: Object {
}

The last two structs get the identifier property "mixed" in but are unique
types :slight_smile:

Is unique meant so that User-Objects can't be used as "Object"-Object
parameters?
I'm asking because this might be the biggest source of confusion with
struct inheritance:
As the two "child-structs" don't add new data to their parent, they would
(technically) be compatible — but this is fragile, so it might be
preferable to "hide" polymorphism by default, and maybe add an annotation
to explicitly allow that a sub-struct can be used as its parent type.
@compatible(Float) struct SpecialFloat: Float...