-
-
Notifications
You must be signed in to change notification settings - Fork 1.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
Allow multiple annotations in the same annotation block #11483
Comments
I would say this would cause confusion as current way of annotating is how other languages are following the practice. Also this approach will cause clutter when combining multiple annotations which comes with elements. instead it would be much cleaner if one can annotate an annotation and then use that annotation instead. Something like (made up example) @[Nullable]
@[Unique]
annotation MultiAnnotation
end
@[MultiAnnotation]
property mykey |
This syntax should be doable, but I'm not convinced it's a good thing. I would strictly prefer each annotation on a single line. That makes more sense to me to clearly tell them apart. It's easier to miss an annotation if it's hidden behind others. Maybe this can be combined to writing annotations in a list, but formatted with line breaks? @[Nullable,
Unique,
Default(0)
Type(smallint)]
property MyKey
@[
NonNullable,
Default(""),
Type(string)
]
property Data Not sure if that has a reasonable benefit over the original multi-line notation. |
We should allow multiple annotations on the same line: @[Nullable] @[Unique] @[Default(0)] @[Type(smallint)]
property my_key There is no need to expand the syntax of a single annotation node to allow this. |
That's IMO the least readable version of all proposed. |
I don't know if it's the least readable, but I certainly believe the original proposal reads much better |
Putting aside the typical semantic of @[Nullable | Unique | Default(0) | Type(smallint)]
property MyKey
@[NonNullable | Default("") | Type(string)]
property Data Compared to @[Nullable, Unique, Default(0), Type(smallint)]
property MyKey
@[NonNullable, Default(""), Type(string)]
property Data Sure, you can write @[Nullable , Unique , Default(0) , Type(smallint)]
property MyKey
@[NonNullable , Default("") , Type(string)]
property Data But it less readable and different style with commas. |
how about using semi-colons as separator? Some people like me has been grouping related Enum options into a single line like this already, so our eyes are already trained for this.. @[Nullable; Unique; Default(0); Type(smallint)] also, nothing can stop us from using multiple annotations when it's more appropriate, like this: @[NonNullable; Default(0); Type(int)]
@[Unique; UniqueConstraintName("unique_index")]
property name |
To me the brackets make it look like i could put more stuff in there, like in c# and PHP. |
I'm failing to see the issue with the comma-separated list from these examples. I do agree it might be an issue with multiple arguments, but those aren't so common. And the notation |
I'd like to learn a bit more about real use cases. The examples are just made up. In my projects folder, I found only a hand full of more than two consecutive annotations. They are from https://github.com/ujjwalguptaofficial/shivneri and https://github.com/athena-framework/athena, both frameworks which heavily use annotations. I'm showing one example of each. They have the max number of consecutive annotations, but the complexity and length of each single annotation is representative. @[Worker("POST")]
@[Route("/")]
@[Guards(UserValidator)]
@[Inject("as_body")]
@[ExpectBody(User)] @[ATHA::Get("events")]
@[ATHA::ParamConverter("since", converter: ATH::TimeConverter)]
@[ATHA::QueryParam("since")] These real annotations are noticeably longer than those in the made up examples and occupy a higher fraction of the line. Which reduces the space advantage and would affect the readability of collapsed notation in a single line. In my opinion, none of these examples would improve by collapsing the annotations, regardless of which syntax we use. |
My main input here is that in the past years I've seen auto-formatters of all sorts usually converge on a "single thing per line" syntax. In an alternate universe where Crystal already had both syntaxes, I'd expect some amount of complaints coming in from teams being unable to conclude on a consistent way to write these. Even from the main post of this issue, the first example is actually the way that I'd prefer things converged on. And it has the huge advantage of being the only way to write it. And this isn't even fixable by Crystal's formatter, the point of which is to have one undisputed way to write things. |
This is an example were I wanted to put multiple annotations in one block. I made a config class were i wanted to annotate some properties if they could be changed on reload, should be included in the diff between current and new config, and if it can be set via command line argument. I can also imagine that I'd like to add some validation related annotations at some point. It looks something like this (but a lot more options): class Config
@[NoReload]
@[NoDiff]
@[CliOpt("-d dir", "--data-dir=dir", "Data directory")]
property data_dir = ""
@[NoDiff]
property users = Hash(String, String) .new
property log_level = Log::Severity::Info
end IMO it would have been more readable if i could put the simple annotations in on block. Now it almost feels like the properties disappears because of the annotations. |
There is no such thing in programming :-) For instance, there are disputes already towards the use of if condition
something
end vs something if condition To me, shortening the number of lines (without breaking readability) is an important goal. When compared to Java, that was one of the main advantages of Ruby: no visual noise. And like @spuun mentions, annotations stacking up does introduce some noise to the general structure of the program. |
Regarding the examples shown by @straight-shoota , to me the first example benefits clearly from this: But again, it might be my biased love to reducing the number of LOCs. |
The fact that annotation in Crystal requires extra @Nullable @Unique @Default(0) @Type(smallint)
property my_key Or with commas @Nullable, @Unique, @Default(0), @Type(smallint)
property my_key |
I've found the ideal solution: 🔮Nullable 🔮Unique 🔮Default(0) 🔮Type(smallint)
property my_key |
I don't think we should design the language around reducing LOC |
Designing for that? Absolutely no! But there are multiline/single line options already to chose from. For example this looks very clean and natural to me in both forms: # multiline
getter a
getter b
getter c
# single line
getter a, b, c Annotation syntax is less clean as it is and with multiple annotations allowed on the same line it looks even less clean to me (relative to other Crystal code). |
No, but Crystal is designed to be clean, and this is what's this is about (not saying everyone should agree on what clear is in this particular topic). To me, shortening the lines it takes for annotation gives more space to understand the structure of the class, and that leads to cleaner code. But it should not be confused with the opposite "cleaner code -> fewer LOCs"; that's not what I mean. |
If that was the case, #9080 would have been accepted by now |
Yeah, I don't think they're technically necessary. Annotations are unambiguously determinated by parentheses (or whitespace if there are no arguments). Maybe we can remove them or make them optional? 🤔 Then instead of |
Well, what was the reasoning behind this change from 7 years ago? #195 |
BTW, let me add that annotations are akin to C#'s attributes, and in C# you can have multiple of them. |
Right! I think our annotations were inspires by those or Java's. To be honest, I thought it was already possible to specify multiple annotations like that. |
I can barely remember what I did last week, I can't remember why we changed the syntax 7 years ago. I think the old syntax looks very weird. |
Reading more C# attributes examples I like it's syntax where you can combine multiple attributes in different ways like:
I also like that without @[Nullable] @[Unique] @[Default(0)] @[Type(smallint)]
property my_key
@[Nullable]
@[Unique]
@[Default(0)]
@[Type(smallint)]
property my_key vs [Nullable] [Unique] [Default(0)] [Type(smallint)]
property my_key
[Nullable]
[Unique]
[Default(0)]
[Type(smallint)]
property my_key |
My 2 cents, I think it would be worth doing. It's worth pointing out that this new syntax isn't going to replace the old syntax. I.e. I don't think anyone is saying that once/if this feature is added that you must use it. Although having the ability to use it would be helpful. It would allow you to group related annotations by line while still being able to use multiple lines for distinct groups. For example If you had an ORM you could imagine something like: @[Column]
@[Id]
@[GeneratedValue("AUTO")]
getter! id : Int64 with this feature you could rewrite it to: @[Column]
@[Id, GeneratedValue("AUTO")]
getter! id : Int64 So you end up saving a line and also make things more readable by allowing multiple related annotations on each line when it makes sense. |
@vlazar note that |
One thing not mentioned is the ability to ignore the newline between the annotation and the annotated node. This makes some things possible: def fact(x)
if @[Likely] x > 1
x * fact(x - 1)
else
1
end
end Contrast with C++: int fact(int x) {
if (x > 1) [[likely]] {
return x * fact(x - 1);
else {
return 1;
}
}
If we do allow commas within |
@HertzDevil I think your concern is very easy to resolve. Just have the compiler convert comma-separated annotations into multiple annotation nodes, as syntax sugar at very early stages of compilation. |
@qualterz great observation! That's exactly why we chose that syntax. It would be, at least partially 😉, familiar to Java and C# programmers |
Fun fact: Ruby RBS chose |
Currently, each annotation takes its own line, leading to code that is not as comfortable to read. Made up example:
Consider instead the following version:
To me, I can easily read that there are two properties, and I can check the details just if I need to. In the first version, I have to go through more lines to get the most important information.
The text was updated successfully, but these errors were encountered: