When are enums not a code smell?
Generally the enum vs polymorphic class debate is actually a subset of a better known trade-off between OO style inheritance and FP style use of sum types.<p>The trade-off is between ease of extension in two cases:
- adding new types to the sum type &#x2F; new subclasses
- adding new methods that can operate on all types &#x2F; subclasses<p>In a classic OO design, it&#x27;s easy for any client to add new subclasses that implement the same API as the existing classes. However, adding a new API means that all subclasses need to be changed.<p>Conversely, in an FP design, adding a new type to the sum type (a new enum value) means that all places pattern matching on the sum types (switching on the enum) need to be changed. However, adding a new operation that works with the existing types is trivial.<p>To give a more specific example, let&#x27;s say we want to model an expression tree.<p>An OO design might look like this:<p><pre><code>  abstract class BaseExpression {
    abstract evaluate()
    subexpressions()
  }
  class SumExpression extends BaseExpression {
    evaluate() = map(evaluate, subexpressions()).Sum()
  }
  class NumberExpression extends BaseExpression {
    int x
    subexpressions() = [x]
    evaluate() = x
  }
</code></pre>
An FP design might look like<p><pre><code>  type ExpressionTree a = SumExpresssion [ExpressionTree] | NumberExpression a

  evaluate :: ExpressionTree a -&gt; a
  evaluate SumExpression xs = sum map evaluate xs
  evaluate NumberExpression x = x

  subexpressions :: ExpressionTree a -&gt; [a]
  subexpressions SumExpression xs = map union xs
  subexpressions NumberExpression x = [x]

</code></pre>
Equivalently, an enum based design would have<p><pre><code>  enum ExprType {
    Sum
    Number
  }
  
  struct SumExpression {
    ExprType
    *void[] Subexpressions
  }

  struct NumberExpression {
    ExprType
    int x
  }
  
  evaluate(e) {
    switch(e-&gt;ExprType) {
      case Sum: (SumExpression)e.Subexpressions.map(evaluate).Sum()
      case Number: (NumberExpression)e.x
    }
  }

  subexpressions(e) {    
    switch(e-&gt;ExprType) {
      case Sum:  return (SumExpression)e.subexpressions
      case Number: void*[]{(NumberExpression)e.x}
    }
  }
  
</code></pre>
Now, if we want to add support for MultiplyExpression, in the OO program we add a new file and we define evaluate() for MultiplyExpression. In the FP program we have to modify the definitions of ExpressionTree, SumExpression and NumberExpression.<p>Conversely, if we want to add support to print an ExpressionTree, in the FP version we just add a new function in a new file and describe how to print each type of ExpressionTree. In the OO program, we need to modify the implementations of each existing BaseExpression subclass to add support for printing.