Enum(eration)s
Understand what enums are and when you may want to use one.
Oftentimes a variable can only take on a small number of values. This is when an enumeration (enum) would be useful.
enums are values that can only take on a select few variants. They are defined using the enum keyword.
Enums in R
We use enums all the time in R and almost exclusively as function arguments.
The tidyverse design style guide has a great section on enums. See enumerate options.
args(cor)function(
x, y = NULL, use = "everything",
method = c("pearson", "kendall", "spearman") # 👈🏼 enum!
)I’ve written about this in more detail in my blog post Enums in R: towards type safe R
Example
For example, it may make sense to create an enum that specifies a possible shape.
enum Shape {
Triangle,
Rectangle,
Pentagon,
Hexagon,
}Variant-specific behavior
The Shape enum can take on only one of those 4 variants. An enum is created using the format EnumName::Variant.
How do we actually determine behavior based on a variant? This is done using pattern matching.
The keyword match lets us perform an action based on an enum’s variant. It works by listing each variant and the behavior to that happens when that variant is matched.
The pattern match format uses the syntax Enum::Variant => action. When using match each variant much be enumerated:
=> is often referred to as “fat arrow.” But if you say “chompky” I’ll also get it.
match my_shape {
Shape::Triangle => todo!(),
Shape::Rectangle => todo!(),
Shape::Pentagon => todo!(),
Shape::Hexagon => todo!(),
}todo!() is a placeholder that can be used to make the compiler happy
Example
For example we may want to print the number of verticies of a shape:
let my_shape = Shape::Triangle;
match my_shape {
Shape::Triangle => println!("A triangle has 3 vertices"),
Shape::Rectangle => println!("A rectangle has 4 vertices"),
Shape::Pentagon => println!("A pentagon has 5 vertices"),
Shape::Hexagon => println!("A hexagon has 6 vertices"),
}Wildcard matching
Sometimes we want to customize behavior on only a subset of variants. We can use a catch all in the match statement _ => use the underscore to signify “everything else”.
match my_shape {
Shape::Hexagon => println!("Hexagons are the bestagons"),
_ => println!("Every other polygon is mid"),
}Enums can impl, too
Enums can have methods too just like a struct using impl keyword
impl Shape {
fn is_bestagon(&self) -> bool {
match self {
Self::Hexagon => true,
_ => false
}
}
}Exercise 1
- Create an enum called
Measurewith two variantsEuclideanandHaversine - Create a method called
ndim()which returns2forEuclideanand3forHaversine
View solution
enum Measure {
Euclidean,
Haversine,
}
impl Measure {
fn ndim(&self) -> i32 {
match self {
Self::Euclidean => 2,
Self::Haversine => 3
}
}
}Exercise 2
- Create a new method
distance()forPointstruct that returns anf64- Arguments:
&self,destination: &Self,measure: &Measure
- Arguments:
- When
measureisEuclideanuse theeuclidean_distance()method - When the variant is
Haversineuse thehaversine_distance()method
The haversine method is defined as:
Code for haversine_distance()
impl Point {
fn haversine_distance(&self, destination: &Self) -> f64 {
let radius = 6_371_008.7714; // Earth's mean radius in meters
let theta1 = self.y.to_radians(); // Latitude of point 1
let theta2 = destination.y.to_radians(); // Latitude of point 2
let delta_theta = (destination.y - self.y).to_radians(); // Delta Latitude
let delta_lambda = (destination.x - self.x).to_radians(); // Delta Longitude
let a = (delta_theta / 2f64).sin().powi(2)
+ theta1.cos() * theta2.cos() * (delta_lambda / 2f64).sin().powi(2);
2f64 * a.sqrt().asin() * radius
}
}View solution
impl Point {
// Demonstrates using pattern matching an enum
fn distance(&self, destination: &Self, measure: &Measure) -> f64 {
match measure {
Measure::Euclidean => self.euclidean_distance(destination),
Measure::Haversine => self.haversine_distance(destination),
}
}
}