I’m looping back to reading some of Tidy Design Principles since it hasn’t been touched in a while (indicating some stability, at least in the short term). I jumped ahead to a section on enums out of curiosity and pondered on them a bit more - they’re really useful in Rust, maybe there’s a good way to implement them in R.

I did a quick search around the web and found some okay-ish Stack Overflow posts, then stumbled across this post which is just - chef’s kiss - precisely what I wanted. I clearly haven’t been notetaking for long, because I have definitely read this post… and left a comment on it at the time.

Revisiting it again, it’s still a brilliant post that has a really nice modern implementation of enums in R that boils down to

# remotes::install_github("RConsortium/S7")
# or 
# install.packages("S7")
library(S7)

# create a new Enum abstract class
Enum <- new_class(
  "Enum",
  properties = list(
    Value = class_character,
    Variants = class_character
  ),
  validator = function(self) { 
    if (length(self@Value) != 1L) {
      "enum value's are length 1"
    } else if (!(self@Value %in% self@Variants)) {
      "enum value must be one of possible variants"
    }
  }, 
  abstract = TRUE
)

# create a new enum constructor 
new_enum_class <- function(enum_class, variants) {
  new_class(
    enum_class,
    parent = Enum,
    properties = list(
      Value = class_character,
      Variants = new_property(class_character, default = variants)
    ),
    constructor = function(Value) {
      new_object(S7_object(), Value = Value, Variants = variants)
    }
  )
}

GridShape <- new_enum_class(
  "GridShape",
  c("Square", "Hexagon")
)

GridShape

so now I’m left wondering “where to next” for this idea?

One potential downside I can see of capturing argument options in an enum is that, depending on the implementation and how documentation is generated, the documentation may then live in the enum itself.

I’m not sure how one would document the S7 enum above, since it’s the result of a function call, not a definition.

With the current idiom of stringy enum arguments the variants are all spelled out in the function signature, and probably documented there, too. If, instead, the function took a GridShape enum, it might result in the variants being documented in the enum instead. This could lead to disparate implementations (add an unsupported enum variant) or just having to look in two places.

On the other hand, it may also lead to more consistent documentation if that enum is used in multiple places.

There’s a certain efficiency to having users be able to call a function as move(direction = "north") over move(direction = Compass("north")) but maybe that can be solved with syntax, e.g. having the package define & export a symbol North <- Compass("north"). But then again, that obfuscates the origin of North. Having a Compass::North syntax like Rust would be great if the :: wasn’t already signalling a package namespace.

We do already have a syntax for taking a subset; Compass$North would be the most consistent. That doesn’t currently work for the S7 definition

GridShape$Square
# Error: Can't get S7 properties with `$`. Did you mean `GridShape@Square`?

GridShape@Square
# Error: Can't find property <S7_class>[@Square](https://micro.blog/Square)

but maybe that can be solved with some getter (?).

I see some discussions in the S7 repo about support for enums / sum types / literal classes e.g. https://github.com/RConsortium/S7/issues/32 and https://github.com/RConsortium/S7/issues/267 but they predate Josiah’s post.

Has anyone been thinking about enums in R lately?