wishing I could cast (sort of) to protocol, with associated type


(has) #1

Robert Nikander wrote:

Cc:"swift-users@swift.org" <swift-users@swift.org>
Subject: Re: [swift-users] wishing I could cast (sort of) to protocol
  with associated type
Message-ID:<BF76DEE9-ACA5-4A74-A4F0-FEBB84A422A4@apple.com>
Content-Type: text/plain; charset="utf-8"

The only real way to do this today is to have two layers of protocol:

protocol SpecialControllerBase {
   var currentValueBase: SpecialValue? { get }
}
protocol SpecialController: SpecialControllerBase {
   associatedtype SpecialValueType : SpecialValue
   var currentValue: SpecialValueType? { get }
}
extension SpecialController {
   var currentValueBase: SpecialValue? { return self.currentValue }
}

One other option may be to put a typealias in an extension instead of declaring an associated type:

// The almost-a-generic protocol:

protocol AssProtocol {
     var xxx: AssType { get }
}

extension AssProtocol {
     typealias AssType = Any
}

// My concrete classes:

class MyClass1: AssProtocol {
     typealias AssType = Int
     var xxx: AssProtocol.AssType { return 1 }
}

class MyClass2: AssProtocol {
     typealias AssType = String
     var xxx: AssProtocol.AssType { return "two" }
}

// A generic function that uses the aforementioned protocol:

func doit<T>(_ v: T) where T: AssProtocol.AssType {
     print(v, type(of: v))
}

// The rare sweet joy that is 0 compiler errors AND 0 runtime crashes:

let res1 = MyClass1().xxx
let res2 = MyClass2().xxx

doit(res1) // 1 Int.Type
doit(res2) // two String.Type

Needless to say my head is screeching "probable undefined behavior" even as I type this, so caveat emptor, E&OE, don't blame me when it eats your cat and marries your wife, etc. [1] But it did finally get me out of an unspeakably intractable problem with the Swift type system, thus preserving what precious few last shreds of sanity I still possess. Who knows, now that I only have a myriad of all-but-intractable problems left to work through, one day I might even release!

Hope this helps (or at least dies mercifully quickly on you),

has

[1] Blame this guy, 'cos he's who I got it from: https://medium.com/@sunshinejr/what-do-you-really-know-about-typealias-and-associatedtype-5ba25d45848e#.wqpume3n1


(Jordan Rose) #2

To see what this is actually doing, try this instead:

func doit<T>(_ v: T) where T: AssProtocol.AssType {
   print(T.self)
}

(and compare it with a true associated type rather than a typealias)

Associated types preserve static type information. The compiler refuses to let you perform operations on protocols with associated types that would throw that information away. You can't get around that check in today's language.

Jordan

···

On Nov 3, 2016, at 11:07, has <hengist.podd@nuggle.uk> wrote:

Robert Nikander wrote:

Cc:"swift-users@swift.org" <swift-users@swift.org>
Subject: Re: [swift-users] wishing I could cast (sort of) to protocol
  with associated type
Message-ID:<BF76DEE9-ACA5-4A74-A4F0-FEBB84A422A4@apple.com>
Content-Type: text/plain; charset="utf-8"

The only real way to do this today is to have two layers of protocol:

protocol SpecialControllerBase {
  var currentValueBase: SpecialValue? { get }
}
protocol SpecialController: SpecialControllerBase {
  associatedtype SpecialValueType : SpecialValue
  var currentValue: SpecialValueType? { get }
}
extension SpecialController {
  var currentValueBase: SpecialValue? { return self.currentValue }
}

One other option may be to put a typealias in an extension instead of declaring an associated type:

// The almost-a-generic protocol:

protocol AssProtocol {
   var xxx: AssType { get }
}

extension AssProtocol {
   typealias AssType = Any
}

// My concrete classes:

class MyClass1: AssProtocol {
   typealias AssType = Int
   var xxx: AssProtocol.AssType { return 1 }
}

class MyClass2: AssProtocol {
   typealias AssType = String
   var xxx: AssProtocol.AssType { return "two" }
}

// A generic function that uses the aforementioned protocol:

func doit<T>(_ v: T) where T: AssProtocol.AssType {
   print(v, type(of: v))
}

// The rare sweet joy that is 0 compiler errors AND 0 runtime crashes:

let res1 = MyClass1().xxx
let res2 = MyClass2().xxx

doit(res1) // 1 Int.Type
doit(res2) // two String.Type

Needless to say my head is screeching "probable undefined behavior" even as I type this, so caveat emptor, E&OE, don't blame me when it eats your cat and marries your wife, etc. [1] But it did finally get me out of an unspeakably intractable problem with the Swift type system, thus preserving what precious few last shreds of sanity I still possess. Who knows, now that I only have a myriad of all-but-intractable problems left to work through, one day I might even release!