tech-talks/enum-presentation.md

544 lines
8.9 KiB
Markdown

---
title: The Humble Enumeration
---
# What in the heck is an enum?
<!-- pause -->
```java {0|1|1-2|1-3|1-5}
enum MentalState {
WAIT,
OH_GOD,
WHAT
}
```
<!-- pause -->
## And _why?_
<!-- pause -->
```java {0|1|1-2|1-3|1-5}
enum UsefulFor {
EVERYTHING,
A_FEW_THINGS,
NOTHING_AT_ALL
}
```
<!-- end_slide -->
# What in the heck is an enum?
```java
enum MentalState {
WAIT,
OH_GOD,
WHAT
}
```
## And _why?_
```java
enum UsefulFor {
EVERYTHING, // ???
A_FEW_THINGS, // ???
NOTHING_AT_ALL // ???
}
```
<!-- end_slide -->
# Enumerated types
<!-- pause -->
## An enum is essentially a known set of distinct values.
<!-- pause -->
### A couple of common enums would be things like:
<!-- pause -->
* Playing card suits (HEART, SPADE, CLUB, DIAMOND)
<!-- pause -->
* Days of the week (SUNDAY, MONDAY, etc.)
<!-- pause -->
* Log levels (DEBUG, INFO, WARNING, ERROR, FATAL)
<!-- pause -->
* Etc.
<!-- end_slide -->
# Okay, that's great, but how do I know when to use one?
<!-- pause -->
## (Or, "Booleans? More like, _Fooleans!_")
<!-- pause -->
So, picture this. You've got some code that analyzes a computer network.
<!-- pause -->
There is a lovely Computer _class_:
<!-- pause -->
```java
public class Computer {
String hostname;
double maxPowerConsumption;
boolean on;
}
```
<!-- end_slide -->
And a lovely _function_ that calculates how much power a collection of computers might use:
<!-- pause -->
```java
public double estimateWattage(List<Computer> computers) {
double sum = 0;
for (var computer : computers) {
if (computer.on) {
sum += computer.maxPowerConsumption;
}
}
return sum;
}
```
<!-- end_slide -->
<!-- jump_to_middle -->
# This works great.
<!-- end_slide -->
<!-- jump_to_middle -->
## Until someone says...
<!-- end_slide -->
<!-- jump_to_middle -->
"What about computers in standby?"
<!-- pause -->
# Oh.
<!-- pause -->
# True.
<!-- end_slide -->
# Well, okay, maybe let's add a new _inStandby_ field to Computer?
<!-- pause -->
```java {5}
public class Computer {
String hostname;
double maxPowerConsumption;
boolean on;
boolean inStandby;
}
```
<!-- end_slide -->
## and update our wattage function:
<!-- pause -->
```java {7-8}
public double estimateWattage(List<Computer> computers) {
double sum = 0;
for (var computer : computers) {
if (computer.on) {
sum += computer.maxPowerConsumption;
} else if (computer.inStandby) {
sum += computer.maxPowerConsumption * 0.01;
}
}
return sum;
}
```
<!-- pause -->
That works.
<!-- pause -->
Probably.
<!-- pause -->
But it's starting to look a little ugly.
<!-- pause -->
And, wait. Is a computer in standby... powered on? Or is it off?
<!-- end_slide -->
Hmm...
<!-- pause -->
# No matter how you answer the question, you've got yourself a problem.
<!-- pause -->
## There is now an _invalid_ state that an instance of Computer could end up in.
<!-- pause -->
# Let's say we decide that a computer inStandby is _on._
<!-- pause -->
## That means that having an instance where `.inStandby == true` and `.on == false` is _illegal._
<!-- pause -->
And every part of your code better understand that, because the compiler does ___not.___
<!-- pause -->
### The technical term for this is,
<!-- pause -->
#### "Yucky"
<!-- pause -->
### or
<!-- pause -->
##### "Ew"
<!-- end_slide -->
Hmmmmmm....
<!-- pause -->
# Well. We could try a String.
<!-- pause -->
```java {0|4}
public class Computer {
String hostname;
double maxPowerConsumption;
String powerState;
}
```
<!-- pause -->
## But then, how do we know if Computer's powerState is valid?
<!-- pause -->
### Are we going to _enjoy_ writing code like this?
<!-- pause -->
```java
public static final String IN_STANDBY = "IN_STANDBY";
if (computer.powerState.toUpperCase().equals(IN_STANDBY)) {
sum += computer.maxPowerConsumption * 0.01;
}
```
<!-- pause -->
#### My guess is, not so much.
<!-- end_slide -->
# Instead,
<!-- pause -->
# What if we try an enum?
<!-- pause -->
```java
enum PowerState {
POWERED_ON,
STANDBY,
POWERED_OFF
}
```
<!-- pause -->
## Enums can describe more than two distinct states.
<!-- pause -->
### But they're a lot more strict than a String.
<!-- pause -->
#### And we can use _that_ inside our Computer class.
<!-- pause -->
```java {0|4}
public class Computer {
String hostname;
double maxPowerConsumption;
PowerState powerState;
}
```
<!-- end_slide -->
# Then estimateWattage() cleans up pretty nicely!
<!-- pause -->
```java {6-14}
public double estimateWattage(List<Computer> computers) {
double sum = 0;
for (var computer : computers) {
switch (computer.powerState) {
case POWERED_ON:
sum += computer.maxPowerConsumption;
break;
case STANDBY:
sum += computer.maxPowerConsumption * 0.01;
break;
case POWERED_OFF:
// Do nothing
break;
}
}
return sum;
}
```
<!-- end_slide -->
# And adding a new power state has become a _lot_ easier:
<!-- pause -->
```java {5-7}
switch (computer.powerState) {
case POWERED_ON:
sum += computer.maxPowerConsumption;
break;
case IDLE:
sum += computer.maxPowerConsumption * 0.12;
break;
case STANDBY:
sum += computer.maxPowerConsumption * 0.01;
break;
case POWERED_OFF:
// Do nothing
break;
}
```
<!-- pause -->
## This is pretty much the extent of enum capabilites in C, C++, and C#
<!-- pause -->
### But now _this_ is starting to look repetitive.
<!-- end_slide -->
# So, what if we do some Java?
<!-- pause -->
## Could we store that multiplier on the enum itself?
<!-- pause -->
```java {2-99}
enum PowerState {
POWERED_ON(1.0),
IDLE(0.12),
STANDBY(0.01),
POWERED_OFF(0);
public final double wattageMultiplier;
PowerState(double mult) {
this.wattageMultiplier = mult;
}
}
```
<!-- pause -->
# Hey, we can!
<!-- pause -->
## Java lets us associate any arbitrary data with an enum.
<!-- pause -->
### Though it's good practice to stick with something immutable.
<!-- pause -->
(Also: note the semicolon after the last value. It was optional before!)
<!-- end_slide -->
# Now, estimateWattage() doesn't need to know what the multipliers are!
<!-- pause -->
```java
public double estimateWattage(List<Computer> computers) {
double sum = 0;
for (var computer : computers) {
var power = computer.powerState;
sum += computer.maxPowerConsumption * power.wattageMultiplier;
}
return sum;
}
```
<!-- pause -->
## It just needs to know that a multiplier _exists._
<!-- end_slide -->
# And, what the heck, let's use streams.
<!-- pause -->
```java
public double estimateWattage(List<Computer> computers) {
return computers.stream()
.mapToDouble(computer ->
computer.maxPowerConsumption *
computer.powerState.wattageMultiplier)
.sum();
}
```
<!-- pause -->
## _Functional!_
<!-- pause -->
### If you've futzed with streams a lot, you may notice an opportunity here.
<!-- pause -->
## If Computer had some sort of method for:
<!-- pause -->
```java
computer.maxPowerConsumption *
computer.powerState.wattageMultiplier
```
<!-- pause -->
# We could use that directly in mapToDouble(), via a _method reference_
<!-- end_slide -->
# So, let's define that method.
<!-- pause -->
```java {2-99}
public class Computer {
// snip
public double currentPowerConsumption() {
return maxPowerConsumption * powerState.wattageMultiplier;
}
}
```
<!-- pause -->
## Simple enough.
<!-- end_slide -->
# Cool.
<!-- pause -->
# And that leaves us with this fairly-neat implementation:
<!-- pause -->
```java
public double estimateWattage(List<Computer> computers) {
return computers.stream()
.mapToDouble(Computer::currentPowerConsumption)
.sum();
}
```
<!-- pause -->
## It _almost_ doesn't even need a dedicated method anymore.
<!-- pause -->
### But I'll leave that as an exercise for the reader.
<!-- end_slide -->
# Side note
<!-- pause -->
## In Java, you can also define methods directly on an enum.
<!-- pause -->
### For example, say we still wanted a way to check if a computer is on or off.
<!-- pause -->
#### We could add a method directly to PowerState:
<!-- pause -->
```java {7-99}
enum PowerState {
POWERED_ON(1.0),
IDLE(0.12),
STANDBY(0.01),
POWERED_OFF(0);
// snip
public boolean isOn() {
return this != POWERED_OFF;
}
}
```
<!-- pause -->
#### And that keeps the logic all in one place.
<!-- end_slide -->
# In summary,
<!-- pause -->
## Wait, when should you use enums?
<!-- pause -->
# Basically any time you have a _known_ set of _distinct_ states.
<!-- pause -->
## When should you definitely _not_ use an enum?
<!-- pause -->
### Things like:
<!-- pause -->
* Names
<!-- pause -->
* Numbers
<!-- pause -->
* IPs
<!-- pause -->
* Prices
<!-- pause -->
* Etc.
<!-- pause -->
Because they aren't known beforehand, and there are too many to enumerate yourself.
<!-- pause -->
## So, use an enum when you have at least two states.
<!-- pause -->
# But probably less than a hundred.
<!-- pause -->