Protocols, abstraction, and types

I'm trying to embrace Protocol-Oriented Design, so I've started experimenting with building my architecture using a set of "base classes" implemented as protocols. But I'm not getting very far... What I keep getting stuck on is type mismatches when I try to connect together objects that conform to my "base class" protocols.

Here's a simple example?

protocol TypeA {
    var typeB: TypeB { get set }
}
protocol TypeB {
   
}
struct structTypeB: TypeB {
    func doSomethingAwesome() {}
}
struct structTypeA: TypeA {
    var typeB: TypeB
}
let sTypeB = structTypeB()     // <--- this implements doSomethingAwesome()
let sTypeA = structTypeA(typeB: sTypeB)
sTypeA.typeB.doSomethingAwesome() // <--- error! "no member doSomethignAwesome()"


In fact, the Playground initially seems to think sTypeA.typeB is actually structTypeB, but option-clicking reveals that it's actually not... This, of course, is confirmed by the fact that I can't access the doSomethingAwesome() function.

So, how can I get structTypeA to accept/implement it's "typeB" property as a "structTypeB" (or something conforming to the TypeB protocol)?


Any help greatly appreciated.


J.

protocol TypeA {
    var typeB: TypeB { get set }
}
protocol TypeB {
    func doSomethingAwesome() // <-- Just add this line to get it right.
}
struct structTypeB: TypeB {
    func doSomethingAwesome() { print("Doing something awesome.") } // <-- This is how structTypeB conforms to the TypeB requirements.
}
struct structTypeA: TypeA {
    var typeB: TypeB
}
let sTypeB = structTypeB()
let sTypeA = structTypeA(typeB: sTypeB)
sTypeA.typeB.doSomethingAwesome()

Sure, but this avoids the problem by further specializing the protocol. On the most basic level, I just want the protocol to define the relationships. Ideally, I could create any number of variations on TypeB that add different kind of specialization, without breaking the relationship between TypeA and TypeB.

I'm not sure what you mean. Protocols are not superclasses. Protocols dictate a set of things that can be guaranteed about any type that conforms to them.


So if your protocol is empty, then it doesn't require anything from types that conform to it. And the only thing the compiler can know about all the types that conforms to it is that they conform to it, nothing more. So It can't know of any methods or properties or typealiases of that type, unless it is looking at the concrete type and not the protocol, of course.


The only thing it means for a (concrete) type to conform to a protocol is that it has to conform to it, it has to provide implementations for the methods, properties and typealiases that the protocol demands, or it will fail to conform.


There's no inheritance or anything like that going on, a protocol and the types conforming to it cannot "reach" each other in any way. The protocol specifies an "interface" that the conforming types must have and thus are guaranteed to have, that's it.


And a type can of course conform to many protocols at the same time.

Well, one of the proposals of the Protocol-Oriented Programming talk seemed to be suggesting that we could treat them (somewhat) synonymously to superclasses. Besides, I don't want these relationships to be limited to classes - I'd like the freedom to use structs, for example (as in my sample problem). (By the way, the fact that TypeB was empty wasn't intended to be significant.)

I saw the Protocol-Oriented talk and I thought it was one of the best talks. I think protocols are great and I use them and value types for most things, sometimes a class is needed for the indirection / identity, but I almost never use class inheritance, so in that way I guess that you can say that using protocols can do away with super classes.


However, I can't really understand how you see protocols, what they are and what expectations you have on them, and this makes it hard to explain what they are and what they are not. (I did try in my previous reply.)

I would suggest that you read more about protocols and generics in the Swift book.

One of the most important differences between Objective-C and Swift is that in Objective-C almost everything you do is done dynamically, while in Swift and especially when using protocols, you tend to not do dynamically what can be done statically, which means you will want to make the compiler know as much as possible about your types and your code, which is totally different from Objective-C where everything is resolved at run-time, without static typing.

In Swift, and with protocols, almost everything is about static typing.

Doing everything dynamically (as we are used to from Objective-C) in Swift would mean using classes and AnyObject and dynamic type checking everywhere, which is not something you want to do. In Swift, you do things dynamically only if you really have to.

"Well, one of the proposals of the Protocol-Oriented Programming talk seemed to be suggesting that we could treat them (somewhat) synonymously to superclasses."


No, there is a key difference.


Using a superclass means that the entirety of a subclass is its superclass.

A protocol means that only a portion of a conforming type is the protocol.


It is easy to relate to because a person, which you are, is made up of protocols. You do things that are called the same thing but are not.


protocol BassSlapping{}
extension BassSlapping {
  func slap() {
    // Slap da bass.
  }
}

protocol RobinSlapping {}
extension RobinSlapping {
  func slap() {
    // Slap Robin.
  }
}

struct BassPlayingBatman: BassSlapping, RobinSlapping {}

BassPlayingBatman().slap() // ambiguous; won't compile.
(BassPlayingBatman() as BassSlapping).slap()
(BassPlayingBatman() as RobinSlapping).slap()


We are all trying to work out whether protocols with default implementations should be adjectives or nouns ("Slapper" would make sense in this example). Classes are nouns. Interfaces without implementations are adjectives.

Well, I'm starting to think that I probably see protocols exactly as they are, but just hoped for some unforeseen magic!


What I'd hoped to do was not necessarily to create superclasses, per se, but to create a set of relationships between objects (types, really). Specifically, I'm working on a VIPER variant for our new project, and I wanted to use protocols to specify the relationships between components in each module. This would be pure abstraction, in the sense that there would be no implementations at all, just relationships (so, perfect for protocols). For example, a Presenter might know that it has a FlowController, without knowing anything else about it. This would be implemented at the protocol level. The components would adopt these basic protocols, which would enforce a certain connectivity between components, but the objects conforming to the protocols (the actual implementations) would define their own functionality, given the particular module (use-case). I can't do this, because the object conforming to the protocol always returns the protocol type, not the type of the conforming object assigned the variable ("typeB" in my example code). I'm not necessarily surprised by this, as the getter is pretty clear about the type, I was just hoping to get the type of the actual object (i.e., the struct assigned to typeB), not the protocol. I realize I can always cast it, but that seems like code smell to me... (which was mentioned in that talk, btw)

Yes, that completely makes sense. I do understand the difference... as I say above, I think I was hoping for a little black magic.


But actually, using your casting syntax, I can get the behaviour I was after:

protocol TypeA {
    var typeB: TypeB { get set }
}
protocol TypeB {
   
}
struct structTypeB: TypeB {
    func doSomethingAwesome() {}
}
struct structTypeA: TypeA {
    var typeB: TypeB
}
let sTypeB = structTypeB()
let sTypeA = structTypeA(typeB: sTypeB)
(sTypeA.typeB as! structTypeB).doSomethingAwesome() // <--- yup, that works

(I'm just not sure I'm so wild about the casting.)

Protocols are about capabilities, not nouns. Look at all the protocols in the standard library, many of them ends with -able.


When you implement a concrete type, you can have it conform to a number of protocols which together defines and guarantees the interface of that type's capabilities.

Interestingly (perhaps), the Playground seems to share my dream:


https://dl.dropboxusercontent.com/u/8429426/protocol_thread_image.png


...it seems to think sTypeA.typeB should be a structTypeB as well!

You had tell the compiler explicitly (with your forced type casting) what you didn't want to tell it in the TypeB protocol. And as you say, this is not nice looking.


You can't expect the compiler to magically know stuff about the typeB property's type that simply isn't there (since the TypeB protocol is empty).


What is the point of specifying that typeB is of a type that is conforming to a certain protocol if that protocol doesn't have any requirements? That leaves the compiler knowing almost nothing about typeB's type. The whole point of a protocol is to tell stuff about the types that conform to it; to let the compiler know that all these possible types have this one thing in common, which is that they all conform to this particular "interface" that the protocol declares.


I think the bottomline here is that you are expecting something to be resolved dynamically even though that is not going to happen, or perhaps you are mixing these compiler-vs-runtime-things up in some other way. You have to think about what should be done dynamically (at runtime) and what should be done statically (by the compiler, that can know only what you told it to know). And in Swift you want to do as much as possible statically (while in Objective-C you didn't even think like that, you just did everything dynamically without thinking about it).

The point was to separate the architecture from its implementation. As far as compilation goes, I do explicitly pass sTypeA an instance of structTypeB, so I don't see it as being particularly ambiguous at compile time.

As far as the empty TypeB goes, as I said before, this is just a mock example; it would likely have some more specific requirements (but that's beside the point).


In the actual implementation I'm trying to figure out, TypeA might be an Interactor (i.e., in VIPER), and TypeB might be a Presenter. If I have two use-cases, like UserCreation and TaskCreation, say, then I would create a "UserCreationInteractor: Interactor", and a "UserCreationPresenter: Presenter". Thus the protocol would (at minimum) specify the relationship between UserCreationInteractor and UserCreationPresenter (i.e., fundamentally the same as Interactor to Presenter). Crucially, the actual implementation of UserCreationInteractor and TaskCreationInteractor would likely be quite different.

The point was to separate the architecture from its implementation. As far as compilation goes, I do explicitly pass sTypeA an instance of structTypeB, so I don't see it as being particularly ambiguous at compile time.


Yes, but the TypeB protocol of the typeB property is in effect a type eraser, since that protocol is empty, ie it doesn't say anything at all about all the various possible TypeB-conforming types. If the protocol had contained the ...awesome-method signature, then at least it would have guaranteed that all possible types would have had a method with that signature.


The way you have the TypeB protocol now (empty), you could make any type X conform to it simply by doing this:

extension X : TypeB {}


You could do this for String, Int, Double, etc.


And then you could assign a String to the typeB property. Would you still expect the compiler to accept that you tried to call the ...awesome-method on it?



As far as the empty TypeB goes, as I said before, this is just a mock example; it would likely have some more specific requirements (but that's beside the point).


No, I think this is exactly the point. Without more specific requirements, nothing more specific can be assumed about that property, and thus nothing more specific can be done with it. Your TypeB protocol is almost like the Any protocol, almost nothing at all can be assumed or done with it unless you do runtime type checking on it. Remember that the property can hold any value whose type conforms to that protocol, so if we did the above, it can contain a String, and Int, a Double or your structTypeB. If the TypeB protocol had required the ...awesome-method, then you would have been forced to define that method for String, Int and Double as well, and you could then call it no matter what the typeB property currently had been set to, a String, Int, Double or structTypeB.


BTW, the convention is to always use uppercase for the first letter of any type or protocol.

"The way you have the TypeB protocol now (empty), you could make any type X conform to it simply by doing this:

extension X : TypeB {}


You could do this for String, Int, Double, etc."


Yes, I can certainly see your point.

"Yes, but the TypeB protocol of the typeB property is in effect a type eraser,..."


This is the crucial point.


"BTW, the convention is to always use uppercase for the first letter of any type or protocol."


Yes, sorry, I am aware of that... just hasty typing in a Playground.


I think my error was in trying to use protocols-as-types, so to speak, as a means of implementing a basic architecture. What would make more sense would be to use particular functions to define the architecture, and to include these in my component protocols. So an Interactor, for example, has a certain core set of functions, defined in the protocol, which are specialized for a given Interactor (i.e., a UserCreationInteractor). Which is to say that my mistake was in trying to define the architecture in terms of types, rather than functions, I think.


J.

Protocols, abstraction, and types
 
 
Q