Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should struct types have a shared non-Object supertype. #2396

Open
lrhn opened this issue Aug 9, 2022 · 1 comment
Open

Should struct types have a shared non-Object supertype. #2396

lrhn opened this issue Aug 9, 2022 · 1 comment
Labels
brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form data-classes

Comments

@lrhn
Copy link
Member

lrhn commented Aug 9, 2022

(See #1290 for the similar questions for records.)

Should there be a Struct type which all struct declarations implicitly implement, and which no non-struct type can implement?

It allows recognizing that something is a struct.
That's potentially useful for not using it with an Expando, although I think we should put a static bool isCompatible(Object? value) method on Expando so people don't have to check for 6+ different types. But we can use is Struct inside isCompatible then.
(Should num, String, null and bool implement Struct then? Likely hard, since people use identical with them all the time today. Should records be structs, since they also has struct-like identity behavior?)

Having Struct allows restricting types to only struct types with class C<T extends Struct>. Not sure when that would be necessary, though, since there is no code which works for structs and doesn't for classes.
If anything, we'd want to restrict a type to non-structs, so you can use Expando on them.
Looking at C♯, where Value is a supertype of Object and structs are non-Object Values, that seems to be the more useful distinction. We just happen to have a lot of existing code which assumes that Object? is everything and Object is everything except null.
(If we designed from scratch, I'd put Struct/Value above Object, where Object? is today, and make null a Struct.)

So:

  • Should there be a Struct type? If so:
  • Should it be a subtype of Object? (Probably, because otherwise our existing generics with Object/Object? as bound break down)
  • Should Record be a subtype of Struct? (Possibly, or they should share a common "not a real object" supertype.)
  • Should num, String, bool and/or Null be subtypes of Struct? (Probably not, people depend on identity too much.)
@Levi-Lesches
Copy link

Levi-Lesches commented Aug 10, 2022

  • Should there be a Struct type?

I can't see the harm in introducing a Struct type since it gives people the freedom to do more static checking or write extensions for structs. Like how enums eventually implemented Enum. The difference between Struct and Object (or even Object?) isn't as clear cut as Enum and Object, but like your Expando example, it allows people to be more careful.

  • Should it be a subtype of Object?

Deciding if Struct should be a subtype of Object pretty much boils down to whether you can always use an object instead of a struct or a struct instead of an object. There are places where structs don't work instead of objects, like Expando, but an object can always be considered a non-optimized struct. So Struct should probably be a superclass of Object (but I have a better idea, see below).

A counter-example would be some typed_data equivalent for structs, where you have this space-efficient List implementation that may not work with regular objects (or wouldn't be as efficient). In such a case, having a system to enforce structs over regular objects wouldn't be necessary -- you could always use a regular List, but would be an improvement nonetheless. If Struct were a superclass of Object, there would be no way to do that, save for lints.

  • Should Record be a subtype of Struct? (Possibly, or they should share a common "not a real object" supertype.)

#1290 presents similar arguments to those above: Sometimes, you want to represent a Record semantically; sometimes, you don't care. Just like structs represent any value without specifying identity, whereas objects do, records represent a collection of objects of arbitrary length, whereas objects/structs represent a length of 1. So I'd say Record should be a supertype of Struct.

(If we designed from scratch, I'd put Struct/Value above Object, where Object? is today, and make null a Struct.)

Given the points above, I believe this would be optimal:

     Record 
       | 
     Value
       |
  +----+----+
Struct   Object

This should solve most if not all the problems at the cost of some complexity (although most users can just use Value):

  1. Answers a few questions flying around:
    i. Solves Records: What are the semantics of identity? #2390, just use Object when you need to rely on identity.
    ii. Solves What generated methods should structs provide? #2372, since structs won't inherit from Object.
    iii. Solves What does toString on a record do? #2389, since records won't inherit Object.toString.
    iv. Solves Do tuples need a shared non-Object superclass? #1290, since Record would be a supertype to Object.
  2. All Object/Object? code would still work, just not accept any structs or records. This protects any code that relies on identity, like Expando, and doesn't need to worry about toString.
  3. Inherently represents that structs cannot extend or be extended by objects, without the need for "magic" errors.
  4. You can use Value as the new Object/Object? (null can be a Value) to specify that you don't care about the differences in the identity of the value, just like how Object doesn't discriminate on the type.
  5. Since all Values are Records, you can simply pass any Value as an argument to represent a 1-element Record. No need for myFunc( (value,) ). This is nice since records are similar to parameter lists in the first place.
  6. You could specifically request a Struct, which is useful for optimizations like StructList and when writing struct-specific extensions. Maybe there could be macros that would generate code specifically for structs that might depend on how structs cannot be extended.
  • Should Record be a subtype of Struct? (Possibly, or they should share a common "not a real object" supertype.)

Maybe I'm missing something, but it sounds like "not a real object" would mean it's more useful to ask, "is this an Object?" than "is this a Record or a Struct?" In that case, it wouldn't matter how Record and Struct are related, just that they cannot be used where an Object is specifically requested (which means they should not be subtypes of Object).

  • Should num, String, bool and/or Null be subtypes of Struct?

I think they should stay as regular objects since not needing to care about primitives vs objects is nice, unlike how Java always makes us think about Array vs List and int vs Integer. But they technically could fall under Value, and then it would be up to classes like Iterable to decide whether to accept any Value or just Objects.

@eernstg eernstg added the brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form label Oct 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form data-classes
Projects
None yet
Development

No branches or pull requests

3 participants