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

Consistent capitalisation of enum values/constants #7270

Open
dwightwatson opened this Issue Jan 5, 2019 · 11 comments

Comments

Projects
None yet
5 participants
@dwightwatson
Copy link
Contributor

dwightwatson commented Jan 5, 2019

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.

@straight-shoota

This comment has been minimized.

Copy link
Member

straight-shoota commented Jan 5, 2019

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:

  • PascalCase is actually less expressive because not all characters have upper and lower case variants. These characters are unable to denote word boundaries which can lead to ambiguities. In pure PascalCase, an enum member like 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).
  • UPPER_SNAKE_CASE aligns perfectly with lower_snake_case which is used in the auto generated predicate methods on enum types. Following up on the previous point this avoids any confusion whether the respective predicate method for Foo1 is #foo1? or #foo_1?.
  • Enums are often used for mapping C constants, which are usually UPPPERCASED. Thus it is natural to follow the same naming convention for the equivalent enum members and not having to care how to transcribe to PascalCase.

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.

@RX14

This comment has been minimized.

Copy link
Member

RX14 commented Jan 5, 2019

  • Colorize uses PascalCase
  • Logger uses CAPS
  • Regex::Options uses CAPS (because PCRE does), but few people interact with that enum
  • Process uses PascalCase
  • UUID uses PascalCase
  • IO uses PascalCase
  • Socket uses CAPS (because C does), but few people interact with the socket type enums
  • Time 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 either
  • HTTP uses PascalCase
  • LLVM uses a lot of enums, all PascalCase
  • Unicode uses PascalCase
  • YAML uses CAPS
  • WebSocket uses CAPS, but not in user-facing code
  • OpenSSL 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.

@Sija

This comment has been minimized.

Copy link
Contributor

Sija commented Jan 5, 2019

@RX14 Loosing detail in conversion is pretty objective argument. I agree with that too.

@asterite

This comment has been minimized.

Copy link
Contributor

asterite commented Jan 5, 2019

Also remember that the autogenerated flag enum members are None and All. PascalCase is what I expect enums to use.

@dwightwatson

This comment has been minimized.

Copy link
Contributor

dwightwatson commented Jan 6, 2019

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.

@asterite

This comment has been minimized.

Copy link
Contributor

asterite commented Jan 6, 2019

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.

@Sija

This comment has been minimized.

Copy link
Contributor

Sija commented Jan 6, 2019

I regret that decision and we are still in time to remove them.

@asterite what makes you regret it?

@RX14

This comment has been minimized.

Copy link
Member

RX14 commented Jan 7, 2019

@asterite you don't want to remove the generated constants because of their case though.

@asterite

This comment has been minimized.

Copy link
Contributor

asterite commented Jan 7, 2019

@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).

@Sija

This comment has been minimized.

Copy link
Contributor

Sija commented Jan 7, 2019

Better example of flags enum members would be rather Italic, Bold and Underline, in which having All makes a perfect sense to me.

@straight-shoota

This comment has been minimized.

Copy link
Member

straight-shoota commented Jan 7, 2019

Let's continue the discussion about autogenerated enum members in #7285

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment