Single Case Enums in Swift?

I watched Domain Modeling Made Functional by Scott Wlaschin yesterday where he shows how to do Domain Driven Design in F#. He has the slides and a link to his DDD book up on his site as well.

One thing that stood out to me was the pervasive use of single case choice types, which is a better choice than a typealias for modeling.

To see the issue with typealias, consider modeling some contact info. Here is a Swift version of the F# code he showed.

typealias Email = String

The problem comes when you want to model phone numbers, you might do

typealias PhoneNumber = String

The Swift type checker doesn’t distinguish between Email and PhoneNumber (or even Email and String). So, I could make functions that take an Email and pass a PhoneNumber.

I think I would have naturally just done this:

struct Email { let email: String }
struct PhoneNumber { let phoneNumber: String }

But, Scott says that F# programmers frequently use single case choice types, like this

enum Email { case email(String) }
enum PhoneNumber { case phoneNumber(String) }

And looking at it now, I can’t see much difference. In Swift, it is less convenient to deal with the enum as Swift won’t know that there is only one case, and so I need to switch against it or write code that looks like it can fail (if I use if case let). I could solve this by adding a computed variable to the enum though

var email: String {
  switch self {
    case .email(let email): return email
  }
}

And now they are both equivalently convenient to use, but that computed variable feels very silly.

What it might come down to is what kind of changes to the type you might be anticipating. In the struct case, it’s easier to add more state. In the enum case, it’s easier to add alternative representations.

For example, in Sprint-o-Mat, I modeled distance as an enum with:

public enum Distance {
    case mile(distance: Double)
}

I knew I was always going to add kilometers (which I eventually did), and this made sure that I would know every place I needed to change.

If I had done

public struct Distance {
    let distance: Double
    let units: DistanceUnits
}

I could not be 100% sure that I always checked units, since it would not be enforced.

So, in that case, knowing that the future change would be to add alternatives made choosing an enum more natural. This is also reflected in the case name being miles and not distance. It implies what the future change might be.

Even so, I don’t think single case enums will replace single field structs for me generally, but they are worth considering.