MattDiephouse

I'm passionate about writing great software. Find me on GitHub and Twitter as mdiep.

Most Recent

  1. » Type-Driven Development with Swift
  2. » Logic Programming in Swift
  3. » Taking on Technical Debt

All Articles

Type-Driven Development with Swift

23 May 2017

I recently started reading Type-Driven Development with Idris, by Edwin Brady. It’s been enlightening to learn about Idris’s dependent types; I highly recommend it. I've found the book to be very approachable.

In Type-Driven Development, as presented in the book, you focus on the types of your functions before implementing them. After the types compile, you go back and write the actual code. This gives you a proven foundation for your code. (Proven because types are proofs.)

Idris does this with holes, which fill in for an expression that you haven't written yet.

module Main

main : IO ()
main = putStrLn ?greeting

?greeting is a stand-in for the value that’s going to be printed. Idris will report the type of the hole. In this case, ?greeting is a String.

Swift doesn't have holes (maybe it should??), but I’ve found myself using a very similar technique with fatalError. I can figure out my types and then fill in the implementations. This is particularly useful when I’m unsure how to represent a concept in the type system.

enum Result<T, E: Error> {
    case success(T)
    case error(E)

    func andThen<U>(_ action: (T) -> Result<U, E>) -> Result<U, E> {
      // I can work out the types before I write this
      fatalError()
    }
}

let x = Result  // Oops! `T` and `E` can't be inferred.
    .success(3) // I can figure this out before implementing `andThen`.
    .andThen { value in
        return .success("foo")
    }

This works at the top level of your program. But it can also work as you implement a given function:

extension Result {
    func andThen<U>(_ action: (T) -> Result<U, E>) -> Result<U, E> {
        switch self {
        case let .success(value):
            return action(value)
        case .failure:
            // I can come back to this after finishing the `.success` case
            fatalError()
        }
    }
}

This has been a very handy tool for me in the past month. Type inference can be hard to predict, and this lets me work through an API without spending the time implement it.