28

In the following code, I want to test if x is a SpecialController. If it is, I want to get the currentValue as a SpecialValue. How do you do this? If not with a cast, then some other technique.

The last line there won't compile. There error is: Protocol "SpecialController" can only be used as a generic constraint because it has Self or associated type requirements.

protocol SpecialController {
    associatedtype SpecialValueType : SpecialValue
    var currentValue: SpecialValueType? { get }
}
...
var x: AnyObject = ...
if let sc = x as? SpecialController {  // does not compile
BaseZen
  • 8,650
  • 3
  • 35
  • 47
Rob N
  • 15,024
  • 17
  • 92
  • 165

3 Answers3

28

Unfortunately, Swift doesn't currently support the use of protocols with associated types as actual types. This however is technically possible for the compiler to do; and it may well be implemented in a future version of the language.

A simple solution in your case is to define a 'shadow protocol' that SpecialController derives from, and allows you to access currentValue through a protocol requirement that type erases it:

// This assumes SpecialValue doesn't have associated types – if it does, you can
// repeat the same logic by adding TypeErasedSpecialValue, and then using that.
protocol SpecialValue {
  // ...
}

protocol TypeErasedSpecialController {
  var typeErasedCurrentValue: SpecialValue? { get }
}

protocol SpecialController : TypeErasedSpecialController {
  associatedtype SpecialValueType : SpecialValue
  var currentValue: SpecialValueType? { get }
}

extension SpecialController {
  var typeErasedCurrentValue: SpecialValue? { return currentValue }
}

extension String : SpecialValue {}

struct S : SpecialController {
  var currentValue: String?
}

var x: Any = S(currentValue: "Hello World!")
if let sc = x as? TypeErasedSpecialController {
  print(sc.typeErasedCurrentValue as Any) // Optional("Hello World!")
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
1

[Edited to fix: : SpecialValue, not = SpecialValue]

This is not possible. SpecialValueController is an "incomplete type" conceptually so the compiler cannot know. SpecialValueType, although it is constrained by SpecialValue, it is not known until it is determined by any adopting class. So it is a really placeholder with inadequate information. as?-ness cannot be checked.

You could have a base class that adopts SpecialController with a concrete type for SpecialValueController, and have multiple child classes that inherit from the adopting class, if you're still seeking a degree of polymorphism.

BaseZen
  • 8,650
  • 3
  • 35
  • 47
  • The fact that `currentValue` is a `SpecialValue` is not something that can be changed by any adopting class. You are guaranteed that the currentValue is a `SpecialValue` or a subtype of it. – Rob N Nov 02 '16 at 19:42
  • @RobN If you don't take my word for it, see this very similar issue where a 320k rep guy explains the same: http://stackoverflow.com/questions/39629358/why-is-this-causing-so-much-trouble-protocols-and-typealiases-on-their-associa/39629635#39629635 – BaseZen Nov 02 '16 at 19:49
0

This doesn't work because SpecialController isn't a single type. You can think of associated types as a kind of generics. A SpecialController with its SpecialValueType being an Int is a completely different type from a SpecialController with its SpecialValueType being an String, just like how Optional<Int> is a completely different type from Optional<String>.

Because of this, it doesn't make any sense to cast to SpecialValueType, because that would gloss over the associated type, and allow you to use (for example) a SpecialController with its SpecialValueType being an Int where a SpecialController with its SpecialValueType being a String is expected.

As compiler suggests, the only way SpecialController can be used is as a generic constraint. You can have a function that's generic over T, with the constraint that T must be a SpecialController. The domain of T now spans all the various concrete types of SpecialController, such as one with an Int associated type, and one with a String. For each possible associated type, there's a distinct SpecialController, and by extension, a distinct T.

To draw out the Optional<T> analogy further. Imagine if what you're trying to do was possible. It would be much like this:

func funcThatExpectsIntOptional(_: Int?) {}

let x: Optional<String> = "An optional string"
// Without its generic type parameter, this is an incomplete type. suppose this were valid
let y = x as! Optional
funcThatExpectsIntOptional(y) // boom.
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • My associated type has a constraint on it. I know that whatever `currentValue` is, it must be a `SpecialValue` or a subtype of it. So with your `Optional` analogy, what I'm really trying to do is... given an `Optional` where I know `X : SpecialValue` just get the value out of it. – Rob N Nov 02 '16 at 19:46
  • That's not a constraint. It's a default. – BaseZen Nov 02 '16 at 19:47
  • @BaseZen Notice that I use a colon, not an equals sign. If you adopt `SpecialController`, then `currentValue`'s type must be `SpecialValue` or a subtype. – Rob N Nov 02 '16 at 19:57
  • 1
    Not a problem in Obj-C however. :) – nielsbot Nov 02 '16 at 20:04
  • @nielsbot Obj-C's weak typing is far far away from where this is at :p – Alexander Nov 02 '16 at 20:22
  • Ah I see. Misread that. However, we're still both right. :-) The point remains that a protocol with an associated type is considered incomplete. – BaseZen Nov 02 '16 at 20:23
  • @RobN That constraint doesn't address the issue. It limits the domain of possible concrete types `SpecialController ` can be, but `SpecialController ` still represents a set of multiple separate types, one per every `SpecialValue` type. – Alexander Nov 02 '16 at 20:23
  • 1
    I'm don't mean to argue with either of you about what Swift thinks. I'm just explaining that what I'm saying is logically sane, so it seems their could be a way to say it the language. That's why I can do it in, for example, Java, with runtime type-checked casts. – Rob N Nov 02 '16 at 20:28
  • @AlexanderMomchliov Of course. But note that `as?` is being used.. this is a check that can fail at runtime. Anyway, no need to dwell on that in this thread. – nielsbot Nov 02 '16 at 20:29
  • @RobN It's actually not sane, and Java should *not* allow it. The only reason it does is because its generics system is a bodged compile-time abstraction over the `Object` type. Once your generalized code compiles, it's all `Object` under the hood, with unrestricted ability to do run time casts and such. You can achieve the same in Swift with `Any`... but you shouldn't. – Alexander Nov 02 '16 at 20:30
  • @RobN Would you agree that `Array` is an incomplete type? – Alexander Nov 02 '16 at 20:32
  • Sure, "incomplete", or I've heard other terms like "higher kinded" or "existential". Either way, does it not make logical sense to ask "Is x an Array?" without specifying the type parameter to Array? That question has a clear true/false answer for all objects/values, right? – Rob N Nov 02 '16 at 20:38
  • @RobN Yes, it's kind of like a higher kinder a type, a type constructor that takes a type parameter (the element type) and uses it to construct your new array type. The question isn't quite valid, because `Array` on its own is not a type. `Array of something` is a type, and perhaps you don't care for what that "something" is, so it can be "anything". You can perform this check in swift: `let isArray = ([1, 2, 3] as Any) is Array` – Alexander Nov 02 '16 at 20:49
  • @RobN So if you agree that `Array` is an incomplete type, what about `SpecialController`? If I tell you I have an instance of `SpecialController`, have I provided sufficient type information to explain all aspects of the behaviour of `SpecialController`? – Alexander Nov 02 '16 at 20:51
  • No, but you provided enough that we know it has a property called `currentValue`, whose value is of type `SpecialValue`, or a subtype. I should be able to get at that value, somehow. – Rob N Nov 02 '16 at 21:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/127222/discussion-between-alexander-momchliov-and-rob-n). – Alexander Nov 02 '16 at 21:01