I'm constantly running into problems with protocols that have type members (i.e. associated types of protocols). One observation I made while finding workarounds, is that the lack of type parameterization forces me to expose implementation details that I do not want to expose. I find this highly annoying, especially given that one of the use cases of protocols, is to abstract over the concrete implementation of data types. I am looking for some advice on best practices when it comes to using such "generic protocols."
Here is a hypothetical example outlining the problem. Assume we want to define a protocol for a one place buffer. I believe this is what a typical Swift 2 programmer would write:
protocol BufferType {
typealias Element
func getValue() -> Element?
mutating func setValue(value: Element)
}Here is a simple generic implementation of this procotol:
struct OnePlaceBuffer<T>: BufferType {
var value: T? = nil
func getValue() -> T? {
return value
}
mutating func setValue(value: T) {
self.value = value
}
}With this implementation, it's straightforward to create instances of type BufferType:
var buffer = OnePlaceBuffer<Int>()Now let's assume we would like to implement a simple wrapper that implements BufferType and forwards all methods to another object implementing the 'BufferType' protocol. This was my first attempt:
struct Buffer<T>: BufferType {
private var delegatee: BufferType
func getValue() -> T? {
return delegatee.getValue()
}
mutating func setValue(value: T) {
delegatee.setValue(value)
}
}This is, unfortunately, not legal Swift, because 'BufferType' is a protocol with an associated type requirement. Those protocols can't be used as property types (like in line 2). So, this clearly won't work.
I finally managed to implement a buffer wrapper by exposing the type of the delegatee as a type parameter of the wrapper. Here's the code:
struct Buffer2<T, B: BufferType where B.Element == T>: BufferType {
private var delegatee: B
func getValue() -> T? {
return delegatee.getValue()
}
mutating func setValue(value: T) {
delegatee.setValue(value)
}
}This is obviously very ugly, because now, the wrapper isn't hiding the type of the delegatee object anymore. Furthermore, my buffer wrapper has two type parameters making it even more difficult to handle. I eventually found a workaround by using closures (i.e. not wrapping another object, but just storing references to the other objects methods):
struct Buffer3<T>: BufferType {
private var _getValue: () -> T?
private var _setValue: (T) -> Void
init(getValue: () -> T?, setValue: (T) -> Void) {
self._getValue = getValue
self._setValue = setValue
}
func getValue() -> T? {
return _getValue()
}
func setValue(value: T) {
_setValue(value)
}
}For creating a wrapper, a programmer would now have to write code like this:
var wrapper = Buffer3(getValue: buffer.getValue, setValue: buffer.setValue)Interestingly, this does not compile. For reasons that are unclear to me, the Swift compiler tells me that "partial application of 'mutating' method is not allowed". This is referring to 'buffer.setValue'. Obviously, there's a trivial workaround by wrapping the whole thing in another closure:
var wrapper = Buffer3(getValue: buffer.getValue, setValue: { v in buffer.setValue(v) })I find this solution totally messy. Is there really nothing better?
A second example (I'm not going into detail this time) is related to functions returning objects of such generic protocols. For instance, assume we would want to model a Set type in Swift (you won't find one in the standard library for good reasons). My first attempt at this looked like this:
protocol SetType {
typealias Element
func intersect<S: SetType where S.Element == Element>(other: S) -> SetType
}Again, the compiler complained about me using 'SetType' in a place where it wasn't allowed (the result type of 'intersect'). So, I rewrote the whole thing like this:
protocol SetType2 {
typealias Element
func intersect<S: SetType, R: SetType where S.Element == Element, R.Element == Element>(other: S) -> R
}This does compile, but I have not found any way to implement this protocol. This is because type parameter 'R' comes in from the client, but I would like to decide in the implemention of 'intersect' what 'SetType' implementation to return. This is where I am stuck now. Any ideas? Any advice? Am I really forced to define a concrete (non protocol) return type whenever I'm dealing with protocols that have associated type requirements?
== Matthias