With language editions on the horizon, I thought it might be worth taking another look at this idea, perhaps with a slightly broadened scope. Let me know what you think.
What & why?
For those not familiar, let's say you have a situation like this:
enum Font{ normal, heading, caption }
struct Colour{ float r,g,b,a=1f; }
class TextStyle{
private Font font_;
private Colour fg, bg;
TextStyle font(Font val){ font_ = val; return this; }
TextStyle foregroundColour(Colour val){ fg = val; return this; }
TextStyle backgroundColour(Colour val){ bg = val; return this; }
}
void main(){
auto textStyle = (new TextStyle)
.font(Font.heading)
.foregroundColour(Colour(1f, 0.5f, 0.5f))
.backgroundColour(Colour(0f, 0f, 1f));
}
If you look at main
, you'll see that we're repeating ourselves a lot to call those member functions. What if we weren't forced to write the types each time? Then it could be more like this:
void main(){
auto textStyle = (new TextStyle)
.font(.heading)
.foregroundColour(.(1f, 0.5f, 0.5f))
.backgroundColour(.(0f, 0f, 1f));
}
Boom, that's it. For any context where an enum literal or struct literal is being assigned/passed/etc. to something with a known type, allow us to omit the type from the literal. I'm not really set on any particular syntax, but here are some thoughts:
- The
.
syntax feels natural for enum literals, but not for struct literals. This would have to mean changing the module scope operator (perhaps to./
? (Like to how./
represents 'here' in a terminal) because otherwise module-level symbols would interfere. - My original proposal used
$
, which I still like for struct literals, but it's a bit random. - I don't like the C initialiser syntax (
{}
) but it could be re-purposed for type-inferred struct literals, which would mean we can basically merge the two.
Implementation
The original enum literal inference DIP was basically fully implemented in the end and could easily be resurrected.
Essentially, you should be able to use inference in any situation where the compiler can trivially infer the type from the context of the literal. Here's a few examples with enums:
enum Enum { foo,bar }
enum Mune { foo,far }
auto x = .bar; //ERR: can't use type inference when there is no type specified anywhere!
auto x = Enum.foo;
x = .bar; //OK: `typeof(x)` is an enum with member `bar`
x = .car; //ERR: no member `bar` in enum type `Enum`!
void myFn(Enum x){}
myFn(.bar); //OK: the type of the parameter is an enum with member `bar`
myFn(.car); //ERR: no member `bar` in enum type `Enum`!
void myOverload(Enum x){}
void myOverload(Mune x){}
myOverload(.foo); //ERR: `foo` is a member of both `Enum` and `Mune`!
myOverload(.bar); //OK: first overload selected based on `Enum` having member `bar`
The specifics of how this interacts with existing features (e.g. array literals) very much depends on how those features infer type. Here's a good example:
Enum getEnum() => .bar; //OK: return type is an enum with member `bar`
Enum x = (() => .bar)(); //it's probably not worth trying to make this work
auto y = (int num){
switch(num){
case 0: return Enum.foo;
case 1: return .bar; //this will only work if D's compiler frontend knows the return type of this lambda based on the first return statement.
default: assert(0);
}
}(1);
Other languages
Enum literals having their type contextually inferred is a feature in many modern programming languages like Zig, V, Swift, and Odin. Java has something similar, but it's useless because it only applies to switch
statements.
For struct literals however, the closest thing I can think of is C's struct initialisers.