Unifying structs and interfaces #5102
Replies: 2 comments 18 replies
-
Yes, structs should be more limited and we will add these constraints in the future, but I don't think we should get rid of them because we can't fully express these concerns yet. In my mind the difference between interfaces and structs is that interfaces represent objects that must be passed by reference while structs represent objects that can be passed by-value (by bitwise copying all the data). Currently, we can't really express this constraint in the language but ideally structs should only reference immutable objects. Currently, we don't really have immutability modeled for user-defined types only for the built-in collection types, but once we introduce that, then I would add that constraint to structs. |
Beta Was this translation helpful? Give feedback.
-
The idea of combining structs/interfaces is starting to grow on me. Here's a (slightly modified) proposal that uses a new word, "shape", to try to put aside any preconceived assumptions you have from words like "interface" or "struct". But the name isn't essential to the design. Instead of interfaces or structs, Winglang supports shapes. A shape is a contract that allows you specify that a value has a set of publicly available members. A shape is defined like so: shape AwsInstance {
publicIp: str?;
start: inflight(): void;
stop: inflight(): void;
}
A shape does not have a phase associated with it. But function members defined on the shape may have phases, like any other function annotations. Shapes are implemented by classes. A shape can by satisfied by an instance of a preflight class, and inflight class, or an anonymous class object: class A impl AwsInstance {
publicIp: str?;
inflight start(): void { ... }
inflight stop(): void { ... }
}
// preflight class object
let a: AwsInstance = new A();
inflight class B impl AwsInstance {
...
}
// inflight class object
let b: AwsInstance = new B();
// "anonymous class object"
let c = AwsInstance {
publicIp: nil,
start: inflight () => { ... },
stop: inflight () => { ... },
};
// equivalent to...
/*
class $AwsInstance1 impl AwsInstance {
publicIp: str = nil;
inflight start() { ... }
inflight stop() { ... }
}
let c = new $AwsInstance1();
*/ Shapes can extend other shapes. Shape members can only add members - a shape cannot override members of shapes it extends: shape Foo {
foo: str;
}
shape Bar extends Foo {
foo: str;
} If a shape has preflight methods or if it extends the "construct" shape, then it cannot be implemented by an inflight class: shape IBucket extends construct.Construct {
addObject: (): void; // implicitly preflight
}
inflight class B impl IBucket { // error: members "addObject" of the shape IBucket prevent it being implemented by an inflight class
...
}
// alternate syntax:
construct shape IBucket {
...
} If a function parameter has a shape type, it can accept any value that implements that shape: let restartInstance = inflight (inst: AwsInstance) => {
inst.stop();
inst.start();
}; Shapes can serve additional purposes. One use for shapes are named parameters in method signatures or class constructors: shape FooProps {
prop1: bool;
prop2: inflight (): void;
}
class Foo {
new(props: FooProps) {}
}
new Foo(prop2: inflight () => { ... }, prop1: true); If the json shape Order {
id: str;
items: Array<str>;
}
Order.fromJson(...); The |
Beta Was this translation helpful? Give feedback.
-
Started a discussion in #4961
Reposting my comment below:
In addition to this change, I think we should evaluate the purpose of structs. In terms of type expressiveness, this change makes structs a subset of interfaces. structs no longer serve a purpose that can't be solved by the compiler itself. Why not get rid of structs and support interfaces everywhere structs are used i.e.:
If the concern is "structs are special because they provide a guarantee of no functions/behavior", I would say:
and also a response from @eladb :
there's a fundamental difference between behavioral interfaces and data structures in my mind. i don't think structs and interfaces are the same thing. for example, Struct.fromJson doesn't make any sense on interfaces.
My response to the response:
The key part of this is that the difference is in the mind, they don't actually need to do anything different. Functionally structs and interfaces "can" be the same thing. That fact that you decided that interfaces are behavioral is arbitrary once we allow properties. It means I can create an interface with only properties. Is that behavioral? Sure, but then structs are also behavioral.
If you're saying that because interfaces allow functions, then I would argue it doesn't make sense on structs either because structs can still contain functions (via classes). If you have an interface with only properties, sure why not allow fromJson. Either way, this can be statically determined.
I think we're on the same page with interfaces (no need to repeat that they are behavioral or pointing to anything "fundamental", I understand), my concern is with structs. They aren't limited enough. Structs should not allow classes.
If we don't restrict them further, I would argue there is too much overlap between structs and interfaces and we might as well get rid of structs.
Beta Was this translation helpful? Give feedback.
All reactions