Let's say I have a protocol "ValueProvider", which is just something that provides a value:
protocol ValueProvider
{
typealias ValueType
func valueInContext(context: ValueProviderContext) -> ValueType
}
I have various structs that implement this protocol, such as:
struct Constant<T>: ValueProvider
{
var value: T
init(value: T)
{
self.value = value
}
func value(context: ValueProviderContext) -> T
{
return value
}
}
and many other such (generic) structs.
I have another struct, Thing, that has properties which are value provides of particular type. Ideally, I would like to express it like this:
struct Thing
{
var position: ValueProvider<CGPoint>
var name: ValueProvider<String>
...
}
Unfortunately I cannot do this, because ValueProvider is a protocol (which cannot be generic in current version of Swift), and ValueProvider<String> is illegal as a property type.
It looks to me I basically have two options:
(1) Make a new generic ThingProperty<T> enum, use that as the type of the "position" and "name" properties. The enum will have a value provider as its associated value. I don't like this solution because it forces me to change my model only because the type system cannot express what I want, although the runtime shouldn't have any problem with what I want. It feels like changing my model just to make the typechecker happy, which is absurd.
(2) Instead of "ValueProvider<CGPoint>", I can just use "ValueProvider" as the type of the position (and name) properties. This works and allows me to keep the model simple, but I loose the benefits of static typing to a large degree - the system doesn't know that the "position" and "model" properties can only hold CGPoint and String value providers, respectively. As a consequence, when I call valueInContext method on those properties, type checker wouldn't know the correct type of the return value.
What's the "correct" approach to take here? Am I missing something?
Mike, you do point out a really problematic shortcoming of Swift's current type system. I've written about it in an earlier thread with the title "Workarounds for lack of parameterized protocols?".
The folks from Apple working on the standard library were facing this problem all the time as well. They invented a design pattern as a workaround. I'll explain it using your example but using Apple's naming conventions.
Let's assume you want to define a protocol like ValueProviderType which abstracts over a type. You do this in Swift via an associated type:
protocol ValueProviderType {
typealias Value
func valueAtTime(time: Time) -> Value
}
This protocol isn't very useful as a type. You can basically only use it as a type constraint, but not standalone. This limitation can be worked around (with a hack) by defining a struct AnyValueProvider which wraps any implementation of ValueProviderType. This struct hides the concrete implementation of the value provider while providing a usable standalone type. Here's a way to implement AnyValueProvider:
struct AnyValueProvider<T>: ValueProviderType {
let _valueAtTime: (Time) -> T
init<V: ValueProviderType where V.Value == T>(_ delegatee: V) {
_valueAtTime = delegatee.valueAtTime
}
func valueAtTime(time: Time) -> T {
return _valueAtTime(time)
}
}
I have not found a good general way to implement such wrappers, but the approach above using closures should be good enough in most cases (this is discussed in the thread "Workarounds for lack of parameterized protocols?").
Now with AnyValueProvider, you finally have a useful type which you have to use whenever you want to refer to a variable, a parameter, etc. of a concrete value provider. Here's an adaptation of your code:
typealias Time = Int
typealias Color = String
struct Car {
var color: AnyValueProvider<Color>? = nil
var position: AnyValueProvider<Double>? = nil
init () {}
func draw(time: Time) { }
}
Your existing value provider implementations don't need to be changed. They can stay:
struct ConstantValue<T>: ValueProviderType {
var value: T
init(_ value: T) {
self.value = value
}
func valueAtTime(time: Time) -> T {
return value
}
}
struct TimeDependentValue<T>: ValueProviderType {
var min: T
var max: T
init(_ min: T, _ max: T) {
self.min = min
self.max = max
}
func valueAtTime(time: Time) -> T {
return min // just for the example
}
}
Whenever you need to create an instance of AnyValueProvider, you can simply pass your real value provider implementation to its constructor:
var carA = Car()
carA.position = AnyValueProvider(ConstantValue<Double>(123))
carA.color = AnyValueProvider(ConstantValue<Color>("red"))
var carB = Car()
carB.position = AnyValueProvider(TimeDependentValue<Double>(0, 123))
carB.color = AnyValueProvider(TimeDependentValue<Color>("red", "green"))
Does this explain how this pattern works? If you want more inspiration on how Apple uses this pattern, you can check out the various Any* classes provided by Apple in the standard library.
And if there's anyone who has a good idea how to implement those Any* wrappers, please let me know. I'm still very interested in this.