Rolling on from some discussion in #7247 it's been pointed out that as it stands there is no convention for the naming of values on an enum. Through the Crystal code base there are both examples of SNAKE_UPPERCASE
and PascalCase
implementations.
module Colorize
enum ColorANSI
Default = 39
Black = 30
# ...
end
end
class CSV::Builder
enum Quoting
NONE
RFC
# ...
end
end
I think it would be ideal if a convention could be established here for the consistency of the codebase.
One option I see as preferable is that constants will be SNAKE_UPPERCASE
and that enum values will be PascalCase
. One additional benefit from this would be possible (generally) to infer if a value is a constant or an enum from its capitalisation.
For example, Crypto::Blowfish::DEFAULT_ROUNDS
would be a constant, but Colorize::ColorANSI::Default
would be an enum.
Related, and not sure if it's possible at all, but if a convention was decided on it would be great if the Crystal formatter could automatically handle the change.
Enum members are semantically like constants, so I don't follow why they should have different naming conventions.
Choosing one style or the other is mostly a question of preference and heritage/sentimentality (others might call it religion). Languages in the Java family typically use UPPERCASE for enum members, whereas C# and related languages use PascalCase. Of the more modern languages, Rust and Go advocate PascalCase, Kotlin accepts both. (These mentions are only referring to the stdlib naming conventions not what the respective language supports)
Crystal, too has previously accepted both, but PascalCase is more dominant. UPPERCASE is mostly but not exclusively used when creating an enum to represent grouped C constants.
While being mostly equivalent, there are a few practical differences:
X86_64
would be X8664
which means loosing detail. Or it would need to insert an underscore after all (could also be a different separator).Foo1
is #foo1?
or #foo_1?
.A common argument against UPPERCASE is that it is screaming and abrasive. While I don't agree to that, I understand that other people might perceive it as such (I can relate to that, as my typografic aesthetics are infuriated by PascalCasing all-cap acronyms). But in the end, I don't think it matters that much. Between autocasted enum values and predicate methods, the actual member names are rarely used outside the enum definition. And in that capacity UPPER_SNAKE_CASE has the practical advantage of similarity to the lower_snake_case names.
Hence, I prefer an UPPERCASE naming convention for enum members.
Colorize
uses PascalCase
Logger
uses CAPS
Regex::Options
uses CAPS
(because PCRE does), but few people interact with that enumProcess
uses PascalCase
UUID
uses PascalCase
IO
uses PascalCase
Socket
uses CAPS
(because C does), but few people interact with the socket type enumsTime
uses PascalCase
CSV
uses PascalCase
and CAPS
File::Info
uses PascalCase
Flate
/Gzip
uses CAPS
(because libz does), but few people interact with those enums eitherHTTP
uses PascalCase
LLVM
uses a lot of enums, all PascalCase
Unicode
uses PascalCase
YAML
uses CAPS
WebSocket
uses CAPS
, but not in user-facing codeOpenSSL
uses PascalCase
It seems that switching everything to CAPS
would cause a lot more breaking changes, in a lot more commonly-used code, than switching everything to PascalCase
. So since the arguments for either are largely subjective, I suggest we go with the easiest change. The largest breaking change would be to Logger
.
@RX14 Loosing detail in conversion is pretty objective argument. I agree with that too.
Also remember that the autogenerated flag enum members are None and All. PascalCase is what I expect enums to use.
I was a fan of @straight-shoota's argument for going all caps - especially in regard to the automatic generation of predicate methods - however having None
and All
by default sure throws a spanner in the works.
But then also know that I would personally remove those generated constants. I regret that decision and we are still in time to remove them.
I regret that decision and we are still in time to remove them.
@asterite what makes you regret it?
@asterite you don't want to remove the generated constants because of their case though.
@asterite what makes you regret it?
When you have a flags enum members, for example Read
and Write
, you generally ask whether it's read and do something, or write and do something. I can't see a need for having None
and All
. And if they are needed you can just write them down, with a meaningful name. Maybe All
could be ReadWrite
in this case. And maybe None
doesn't make sense if it's for a file open mode (just an example).
Better example of flags enum members would be rather Italic
, Bold
and Underline
, in which having All
makes a perfect sense to me.
Let's continue the discussion about autogenerated enum members in #7285
This has been brought up again in #8984, but this issue is older and has already more comments on the topic.
I'd point out that there are several ideas about changing enum
that could also impact the naming of members: symbols vs. enum (#6736), None
and All
(#7285), predicate methods (#8000), issues with name collisions (#7084) and ideas from literal types (https://forum.crystal-lang.org/t/literal-types-like-in-typescript/1510). We should take potential consequences from those ideas into consideration (at least whether/how we want to persue them or not).
Effectively, I wouldn't want to define any style guide for enums before settling on these issues.
UPDATE: It seems I already declared #7285 settled, so we're just waiting for None
and All
to be removed.
@straight-shoota Since PascalCase was voted unanimously, can we go forward with that? What would be the consequences of, say, going forward with PascalCase? Would any of the other issues benefit if we go with SNAKE_CASE? Is it just that SNAKE_CASE is similar to the question method that's generated for enums?
What would be the consequences of, say, going forward with PascalCase?
I don't know. But that's the thing. We should solve the important questions first. I wouldn't want to push a decision now on a topic that really doesn't matter much whether it goes one way or the other and has worked so far with diversive case styles.
I think the decision of the case should be related to the short-band one, to have consistency.
enum E
FirstElement
SECOND_ELEMENT
end
def m(e : E)
end
# All already valid
m :FirstElement
m :SECOND_ELEMENT
m :second_element
For now the status-quo is :snake_case
, which is close but not really UPPER_CASE
.
It makes sense to have the short-band be the last part of the enum type, like :FirstElement
for E::FirstElement
, or :SECOND_ELEMENT
for E::SECOND_ELEMENT
.
Do we prefer to see :PascalCase
or :UPPER_CASE
?
Most helpful comment
Also remember that the autogenerated flag enum members are None and All. PascalCase is what I expect enums to use.