-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Shortcut (type inferrence) for naming enum values #683
Comments
I'll add my own opinions to the cons I could think of:
This is just part of the general tradeoff with function calls in most programming languages. Functions are our vocabulary, and you're expected to remember or intuit roughly what they do and what parameters they take. If we wanted it be clear when reading code what the parameters are, Zig should have used a Smalltalk/Objective-C style for functions. See #479 for a related discussion.
This is the case with variable/const declarations as well: Just as For something like You could say something similar about |
I think the only real use case is for long sequences of enum usage, such as switches, where writing the same I think this problem also applies to structs with const members and functions, like the common
When I put it this way, the features sounds super scary, and I personally vote against. Besides, we already have facilities to mostly eliminate all these long names. Use a local alias:
I claim, that this keeps all the readability of long names as long as the alias is close to the usage. However, if we really wanna eliminate these names, I propose extending the
Right now, we can only use |
I tried looking into what other languages are doing. Both C++ and Nim seems to have some idea of "scoped" (marked with .pure. in Nim, "enum class" in C++) and "unscoped" enums. This seems like a bad compromise to me. How do you decide if an enum should be scoped or not? C# and D seems to have only scoped enums, but infering enum type has been a requested feature in C# for a while. I had a thought: these languages can have multiple functions with the same name, where the actual function is inferred from the type of the parameters. This can make inferring enum type complicated. But Zig seems to go the way of C: one name for one function (in a given namespace). I think this is the right way for a language that aims to be as explicit, simple and close-to-hardware as Zig. But I think it would be wise to leverage this to make the language "nicer" in other areas, such as enum type inferrence, as long as it doesn't lead to bugs or too much confusion. Hejsil: Can you elaborate what you mean by "scary"? To me, scary means that it can lead to bugs. I don't see how this is possible though. I can't see that you could infer the wrong type. To me, the scariest thing is that programmers and library writers don't use enums. That is what will lead to bugs. Having a small fraction of code readers be confused for a few seconds while they look up a function or struct definition is not scary to me, just a bit annoying. The question is how you divide the "annoying to uninformed reader"/"annying to informed reader and writer" ratio. I think the fact that Zig has type inferrence in declarations I don't agree that aliases or "use" helps much. Look at my example from the problem description for instance. I think aliases makes the code harder to read. Instead of knowing that it's doing inferrence, and knowing that you have to look at the function/struct definition for the answer, you now have to look for some random line in the code. Good point about inferring struct types. I wouldn't say that inferring enum types implies inferring struct types, but they are definitely related. I would say there exists arguments for referring struct types if possible, but they're not nearly as strong. It'd be interesting to create a proposal though, just to see what the implications would be. |
Here's an argument for limiting the scope of this proposal to enums with const Foo = enum {A, B, C};
switch (foo) {
.A => 1,
.B => 1,
.C => 1,
} consider this use case: const Endian = enum {Little, Big};
const NativeEndian = if (builtin.is_big_endian) Endian.Big else Endian.Little;
const ForeignEndian = if (builtin.is_big_endian) Endian.Little else Endian.Big;
fn foo(e: Endian) {
switch (e) {
NativeEndian => {
// do something
},
ForeignEndian => {
// do something
},
}
} this use case works today. now consider if we were also wanting to not have the const Endian = enum {Little, Big};
fn foo(e: Endian) {
const Little = Endian.Big;
const Big = Endian.Little;
switch (e) {
Little => {
// do something
},
Big => {
// do something
},
}
} Obviously you would not write this code, but the fact that you can is problematic. Let's dodge this complexity by designing it out of the language. If we rely on |
About the |
I'll also add that Java 5 allows (actually requires) you to omit the qualifiers on enum values in Another con(colliding with @andrewrk's comment above; we were typing at the same time.)
const Endian = enum { Big, Little, };
const Big = Endian.Little; // doesn't look like it should be an error.
const foo: Endian = Big; // ambiguous.
const Little = "unrelated"; // definitely shouldn't be an error.
const bar: Endian = Little; // ambgiuous. You'd expect that the enum member namespace should shadow the other declarations (meaning that See #678 for a possible solution to this problem. Consider adding this to the list of allowed cases for shadowing:
Then you would clear up the ambiguity like this: const foo: Endian = Endian.Big;
const bar: Endian = Endian.Little; If you really wanted to refer to the aliases On the pro sideYour list of examples is in harmony with #287, which is a major proposal that will change lots of subtle semantics in zig. You can rephrase this proposal in the language of #287 like this: "if an expression's result location has a type that is an enum, and the expression is a single identifier, then the enum's value namespace is pushed onto the namespace search stack." This is actually pretty elegant, understandable, and covers all your examples, and more. |
Personally I don't have a big preference on I changed the examples to get a better feel for what that would look like. |
Btw, this is what the example from the problem statement would look like.
Look at how much more that reads exactly like you'd want it too. (I find the second "nrf." reduntant, but that's nitpicking) To elaborate on why this is important: I think microcontroller firmware code is one of the most attractive use-cases for Zig. In these applications a lot of your code will be accessing memory mapped registers. This is generally a pain in the ass in C. Usually the microcontroller vendor will provide a thin C library to access these, but the documentation for these are of varying quality. Usually the datasheet documenting the registers is the best documentations, and you'll end up almost reverse engineering the C library to figure out how to generate the register writes you want. If Zig could make doing direct register writes about as easy as calling functions, this would easily attract a lot of people interested in writing firmware code. If it's as hard or harder than in C, you'll only attract those who have are interested in safety over anything else, which I'm sad to say isn't as many as there should be. |
@skyfex Ye, scary is the wrong word to use, and now that I read more on how this discussion pans out, I'm starting to like the proposal to. My mindset was mostly that, if you can have And yea, let's have the "Infer everything" in some other issue. |
@skyfex would you mind making an issue for the direct writes to register thing? I don't want to lose track of it. |
In the above 2 commits I introduced the new type, updated zig fmt, and implemented implicit casting to enum types. Here's what's left before this issue can be closed:
|
Current Progress
Problem
In Zig we currently have to type out the full "path" to an enum value. I.e., for the following enum:
We have to provide the namespace (if relevant), the enum and the value name:
someNamespace.Type.Ok
orType.Ok
This can get very tedious. This is en example from my testing:
The code can feel overly verbose and repetitive. It also discourages use of enums. People might use integers or booleans instead of a descriptive enum.
Proposal
Whenever Zig can infer the enum type from the context of the code, it should. Instead of writing
Type.OK
, you can just typeOK
or.OK
(one or the other, which one is up for debate)Examples
When declaring a const or variable, you still need the full name:
var foobar = myModule.Type.Ok
When assigning to an already declared varible, you can use the short form:
foobar = .Ok
When assigning to a field in a struct, or instantiting a struct , you can use the short form:
It should also be possible to use with these proposals: #661 and #649
Discussion
Pros:
Cons:
baz(Active, Enabled, On)
is not much more helpful thanbaz(true,true,true)
)A related idea would be to infer namespace names for other things too (like function calls). This should probably be a separate proposal.
Edit: Changed the examples from
Ok
to.Ok
syntaxThe text was updated successfully, but these errors were encountered: