Protocol OrientedProgramming part 2

This post is all about how I achieved stage 1 of the conversion of the Lambda Calculator. It follows on from Protocol Oriented Programming.

I have commented out all of the code in Expression.swift and added a protocol Expressible. All the functions that were “abstract” in Expression become protocol requirements and all of the functions that were in the base class are contained in an extension.

The first problem I hit is that protocols are all or nothing in terms of visibility. Many of the functions in the class were private or fileprivate. This means that I needed to split the protocol to maintain the same levels of encapsulation with Expressible for public consumption and PrivateExpressible for implementation detail. Alternatively, I could make the entire protocol public and allow for extensibility outside of the module. This is attractive, because it gives me an excuse to make it simple and put all of the protocol functionality into Expressible.

Having said the above, I decided to split the required functionality into several parts, for example, everything to do with stringification will be in one protocol, inherited by Expressible and everything associated with β reduction will be in another protocol, inherited by Expressible. There are now five protocols:

  • Stringifiable – a protocol for turning expressions into human readable strings.
  • BetaReducible – a protocol that must be implemented by all β reducible objects.
  • AlphaReducible – a protocol that must be implemented for α reduction.
  • Expressible – a protocol to be adopted by anything that might be part of a lambda expression.
  • Variable – a marker protocol for objects that are variables. This might go away.

As a general rule, anything that was a public function in Expression is now an extension function on the relevant protocol. Anything that was a private function that needed to be overridden by a subclass becomes a function declared in the protocol and that must be implemented in objects that conform to it.

Encoding for Dumping

The only serious problem I encountered with refactoring to use protocols is the problem of encoding expressions for the /dump meta command. An expression is now represented by any object that is Expressible but the encode(to:) method of an encoder is generic and, in Swift, a protocol does not conform to itself.

The fix for the issue turned out to be surprisingly simple. I simply created the following wrapper type:

public struct CodingWrapper: Encodable
{
    let wrapped: Expressible

    init(_ wrapped: Expressible)
    {
        self.wrapped = wrapped
    }

    public func encode(to encoder: Encoder) throws
    {
        try wrapped.encode(to: encoder)
    }
}

Any time we need to encode an Expressible we can just wrap it in a CodingWrapper and encode the CodingWrapper. This includes the top level call to encode, form say, a JSONEncoder and in the encode(to:) functions for Abstraction and Application.

Which is Better?

I had planned to do some performance testing of the protocol oriented code and the traditional class hierarchy to see which ran faster and then choose which version to go forward with. However, the refactoring has produced more navigable code with the separation of areas of functionality into different protocols. We are also now in the position of being able to extend the Lambda calculator with new expression types from outside of the module and, if you forget to implement an abstract function, the compiler tells you, not the runtime.

Some of the above advantages probably could have been applied to the original class hierarchy, but they weren’t, and I’m not doing it again. We’ll be going forward with the new code, which will henceforward be version 2.x of the package.

Further Changes

We are somewhat constrained by using the bare protocol like a type everywhere. This is the reason why I had to introduce the CodingWrapper type. Also, equality testing is a bit bodged because of the restrictions that Equatable demands i.e. because == has Self constraints, we can’t use it.

Were I to use generics everywhere, both of these issues would be resolved but I don’t think they are significant enough to worry about for now.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.