Type Erasure in Swift*

I found a number of great articles about associated types and type erasure, but had a really hard time to apply them to my own situation, as most of them deal with Pokemons, Foo/Bar or animals. With that in mind, I figured I should write about my problem and how I managed to solve it.

My situation

I’m currently working on an application that contains a few basic forms. I just started working on the first one, so I have only two kinds of field: a classic text field and a date picker. I started describing my form field with a protocol. The idea was to think about what I needed from a form field: a method I could invoke to validate the content of my form and a value. Because my value would be a String in my TextField and a NSDate for my date picker, I used an associated type.

enum ValidationResult
{
    case Valid
    case Invalid(errorMessage:String)
}

protocol FormFieldType
{
    associatedtype Type

    var value: Type? { get set }

    func validate() -> ValidationResult
}

Concrete types

Once I had my protocol, I could use them on my 2 form fields. Note that I’m not actually validating my fields and always returning .Valid because I want to keep things simple. I used an enum when I could have returned a Bool but that’s because, as I wrote on my company’s blog, I really love enums.

class TextField: UIView, FormFieldType {
  typealias Type = String

  var value: String?

  func validate() -> ValidationResult {
    return .Valid
  }
}

class DatePicker: UIControl, FormFieldType {
  typealias Type = NSDate

  var value: NSDate?

  func validate() -> ValidationResult {
    return .Valid
  }
}

Validation

To validate my form, the idea was to put all my fields in a collection and perform a reduce to end up with a single Bool telling me if my form was valid or not. Even if you haven’t been doing Swift for a long time, you may have aready ran into this error message that doesn’t tell you much.

let firstName = TextField()
let lastName = TextField()
let date = DatePicker()

let fields: [FormFieldType] = [firstName, lastName, date]

// ⚠️ Protocol 'FormFieldType' can only be used as a generic constraint 
// because it has Self or associated type requirements

In this situation, the compiler is telling you that you can’t create a collection of FormFieldType items because this protocol has an associated type. That’s exactly our sitation here, we are trying to create a collection with 3 elements, 2 of which declared String as their associated type while the last one uses NSDate.

Solution

https://twitter.com/merowing_/status/766755746356822016

Our problem here comes from the associated type and the solution is a very pragmatic one: we need to ignore the associated type because all we care about is the validate method. So, let’s create an object that does just that!

struct AnyField {
    private let _validate: () -> ValidationResult

    init<Field: FormFieldType>(_ field: Field) {
        self._validate = field.validate
    }

    func validate() -> ValidationResult {
        return _validate()
    }
}

One of the great features of Swift is that instances’ methods are actually curried functions. That’s a whole other concept so I would suggest you go and read this article by Ole Begemann. It allows us to keep a reference of the validate method of the FormFieldType and invoke it when we need to. We can now create a Collection of AnyField objects to validate the whole form.

let fields = [AnyField(firstName), AnyField(lastName), AnyField(datePicker)]
if fields.reduce(true, combine: { ($1.validate() == .Valid) && $0 }) {
    print("Form is valid 🎉")
}

I titled this post with a * because like many concepts out there, the way I solved my problem may not be actual type erasure (but then again, the type has been erased). What is sure is that I now have a way to validate a form by dealing with very simple protocols. Should I decide to take it to the next level and separate my field from the view (and I probably will), it wouldn’t take a lot of work.