Enums with Associated Values
Pro-tip, do not delete your hard work. I sadly had to rewrite this page from scratch : /
Derivation
Sometimes you will want to pass along information along with your cases (I will be using case and states interchangeably for this entry). This entails associating values with individual instances of our states. OCaml programmers rejoice–this will be familiar to you. Maybe something like this will ring a bell:
For those that are lost, I'll try to build out a data type to help explain.
Using such a type would look something like this:
Side note: For those that have not read the section on casting yet, the as!
operator converts our value of type any
to the type that follows. This as!
is basically saying I guarantee that this object is actually of this type, so treat it that way.
We have successfully stored two different types in a list! Of course, this isn't literally true because we combined two into one, but we functionally did. This is a pretty weird example that you won't really see outside of functional programming (I think). But what is important to take away is that we are associating some value with some state.
Declaration and Construction
The method we used above with a wrapper struct works, but is dangerous because of the same issues that led us to using enum
s, the user can input nonsensical data because we don't enforce our preconditions. For example, one could do something like:
The way to get around this is with enums with associated values. We do this with the syntax
Okay, let's put it to work. I am going to create an enum
to describe 3D shapes. I'm going to use something called typealias
, which allows me to shorthand things. For example, position and orientation are just going to be a 3D tuple of floats, and I don't want to write that (because I'm lazy), so I'm just going to rename them.
To create an instance of Shape, we simply use the case as a constructor:
Great! But one issue you may find, especially when stacking multiple associations as shown above is that these associations are hard to keep track of. Going back to the declaration is not always going to be easy, and you may not always have comments to say what's what. To fix this, we can add argument names!
Now, when instantiating, we must include the parameter names:
I personally do not like how much extra code this adds, but I do think that the user needs a description of what they're inputting. For this, we use the wildcard (_
) to make our variables anonymous. To be specific, it's not the same level of anonymity that we saw originally where there were literally no names. Here, when you are writing your code, the user will see variable names. But, any reader who comes back to look later, will need to do a tiny tiny bit of fishing to see what this is. So, this is a tradeoff that you will need to consider when making your design decisions.
Usage
Let's create a function to print out the properties of a shape. To do this, we are going to need to distinguish when a certain shape is a sphere, cube, or tetrahedron. Before we could compare through equality. Something like:
But by adding cases with associated values we lose our enum
's conformance to Equatable
. This means that we lose access to ==
. To be fair, how does a compiler know what we want for equality? Should .Sphere(_) == .Sphere
(all spheres are equal) or should it be equal only if they have the same associations? It is easy to do the latter, we can conform our enum to Equatable
: enum Shape: Equatable {...}
. But, this will not work for us. We need to check the case, not for equality of fields.
As before, this can be accomplished with both if statements (kind of) and switch statements. Except, instead of if statements, we are going to use something called if-case-let
. If you have read about optionals, think if-let
and guard let
–if it's the right case, assign it to an identifier. If you haven't read about optionals yet, no fear this will just be a tiny bit weird. Here's what an if-case-let
looks like:
You'll notice we actually have two syntaxes that both work. I don't know of any differences other than keeping the let expressions within the associations has more characters ¯_(ツ)_/¯.
Okay, so what's going on here?
First, we are checking if shape
is a case of Sphere
. If it is, then we bind it's associated values, in order, to position and radius. If not, we continue until it hits the right case, and assign the properties accordingly.
Switch statements can do the same!
Combining Patterns
In some case
s (😉), you may be repeating code. For example, let's create a function to return position of every shape:
Note: The wildcards (_
) here mean that there's a value that we don't care about, so we are going to bind them to nothing.
There's potential for a lot of repeated code here... In some cases, you could refactor some of the work into a function. But, even better, you can actually combine cases! This works if you can meet 3 conditions:
All cases bind the same number of variables
All cases bind the same types of variables
Every case binds the same variable names to each type
If these criteria are met you can combine cases with commas:
Last updated