15 Enum(eration)s
Understand what enum
s 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.
enum
s 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(
y = NULL, use = "everything",
x, 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
Measure
with two variantsEuclidean
andHaversine
- Create a method called
ndim()
which returns2
forEuclidean
and3
forHaversine
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()
forPoint
struct that returns anf64
- Arguments:
&self
,destination: &Self
,measure: &Measure
- Arguments:
- When
measure
isEuclidean
use theeuclidean_distance()
method - When the variant is
Haversine
use 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),
}
}
}