Protocol oriented programming is a programming paradiagm promoted by Swift developers. Coming from a Java or Objective-C background, interfaces are a core language feature used by practically every developer. Protocols are very similar to interfaces, and it is quite easy to dismiss “protocol oriented programming” as just interface oriented programming.

In many ways, protocols are basically interfaces (or traits in Rust), so the core ideas are the same. However, one of the biggest differences is the addition of the ability to retrofit conformance of existing types to new protocols/traits and generic language features. It may muddle the argument that protocol oriented programming is unique due to other language features not named protocol.

Taking a step back, the use of interfaces is primarily applicable when code is interacting between two separate parties (say a framework/library and application code). Using interfaces allows flexibility for method inputs in comparison to concrete implementations such as classes or structs. While interfaces are extremely helpful, the end goal is integrating the code. Protocol oriented programming expands on the toolset to make code integrations easier.

Here’s an example of how protocol oriented programming iterates over interface based programming.

Type Extensions

Type extensions allow anyone to add methods to an existing type with a few restrictions.

Suppose there’s an Entity protocol which declares a name function which returns the name of the entity. Also, there’s a simple function which prints hello with the entity’s name. Both exist in a third party library.

protocol Entity {
  func name() -> String
}

func sayHello(subject: Entity) {
  print("Hello \(subject.name())")
}

If you replace protocol with interface (and some language syntax changes), there does not seem to be any difference between protocols and interfaces.

However, let’s say you have an existing Person type which you wish to pass to the sayHello function.

class Person {
  func firstName() -> String {
    return "Jane"
  }

  func lastName() -> String {
    return "Doe"
  }
}

Now, instead of having to create a wrapper type, you can simply add an extension which makes Person conform to the Entity protocol.

extension Person: Entity {
  func name() -> String {
    return "\(self.firstName()) \(self.lastName())"
  }
}

It could be argued that this is just language syntax sugar and writing a wrapper type is easier. Ultimately it depends on the complexity of the protocol and how you use the underlying instance. If you need to convert between types often and depending on how your compiler can treat the wrapping type, there are drawbacks to wrapper types.

Generics

Generics allow expressive function and type declarations which empower the compiler to better type check the code as well as potentially generate better performing code (aka “generic specialization”).

Following the above examples, the sayHello function can be changed to use a generic type.

func sayHello<T>(subject: T) where T: Entity {
  print("Hello \(subject.name())")
}

While the above example is a trivial use of generics, it allows some compilers to determine if it should create a specific version of the function for every specific type of Entity which is used. So there could be a version of sayHello for the Person type. If there was a Robot type which conformed to the Entity protocol, the compiler may generate machine code for both Person and Robot. The type specific machine code could be faster by directly invoking type specific methods rather than going through the Entity protocol via a lookup table (e.g. vtables).

In a more realistic generic function, the function could guarantee that two parameters are the same type while implementing the same interface(s). A common example is a comparision function which usually is used in more performance demanding code.

Library-Application Integration Reversal

But perhaps you do not care about the convenience of type extensions or the improvements from generics because you control your application code. Afterall, you can easily just open up your class and add the : Entity to cause your Person type to conform to Entity.

Applications call library code and it is generally presumed that libraries should be the party that uses interfaces as library inputs. In reality, the integating code goes both ways. The library code may return concrete or interface types (which cannot be changed by the application code author). Then, the library’s concrete types and interfaces are used like other types which were created in the application code base.

In some sense, the library’s type definitions have polluted the application’s code base. If the library were to change its types/method names or make other breaking changes, the changes could drastically affect the application code base.

For instance, suppose you are writing a web application and are getting query parameters and header values off the HTTP request. The web framework has a standard Request type which provides access to all the properties of an HTTP request. Your function needs to retrieve the name parameter off the request like:

func sayHello(req: Request) {
  print("Hello \(req.getQueryParameter("name"))")
}

The Request library type is now part of your application code. While the above is simple, imagine library types which might be passed throughout functions defined in the application.

The use of type extensions and interfaces/protocols defined in the application code base can negate the impact.

You could declare a EntityParameter protocol, and use type extensions to retroactively conform Request like:

protocol EntityParameter {
  func name() -> String?
}

extension Request: EntityParameter {
  func name() -> String? {
    return self.getQueryParameter("name")
  }
}

func sayHello(subject: EntityParameter) {
  print("Hello \(subject.name())")
}

let incomingRequest: Request = // given by the framework
sayHello(subject: incomingRequest)

Now your function can take a type which implements EntityParameter. The dependence on the library code is less. Furthermore, the function is more easily testable since you can easily create a stub which implements EntityParameter.

The goal of the code above could be accomplished with interfaces and wrapper types (e.g. a new application type which delegates to the Request type and which implements EntityParmeter). I would argue that type extensions require less code. Again, generics can help ensure expressiveness with type safety while improving performance.

Iteratively improving code integrations

In the end, conceptually, “protocol oriented programming” may not be significantly different than programming with interfaces in mind. However, the addition of type extensions and generics make programming with protocols/interfaces more pleasant and can help separately developed code bases evolve together.