Using generic protocols in generic classes - class

I defined a protocol LLNodeType:
protocol LLNodeType {
typealias T
var firstNode: LLNode<T>? { get }
}
LLNode<T> is just a simple generic class, which contains a stored property of type N.
class LLNode<N> {
var content: N
...
}
To conform to the LLNodeType protocol I therefore extended LLNode as follows:
extension LLNode: LLNodeType {
typealias T = N
var firstNode: LLNode<T>? {
return self
}
}
I also defined a generic class LLLinkedList containing a few properties and functions using the generic type L:
class LLLinkedList<L> {
var rootNode: LLNode<L>?
...
}
I extended this class to conform to LLNodeType:
extension LLLinkedList: LLNodeType {
typealias T = L
var firstNode: LLNode<T>? {
return self.rootNode
}
}
I found a way of passing LLNodeType to methods of LLLinkedList as a regular type and used it on the append method:
func append<NT: LLNodeType>(nodeSequence: NT) {
let nodes = LLLinkedList(nodeSequence: nodeSequence)
...
}
As seen in the first statement of the append method, I have also defined an initializer for LLLinkedList, that takes a parameter nodeSequence of type LLNodeType:
convenience init<NT: LLNodeType where NT.T == L>(nodeSequence: NT) {
self.init()
self.rootNode = nodeSequence.firstNode
}
The initializer only takes a nodeSequence which conforms to LLNodeType, which is constrained though to use a type T equal to L.
The firstNode property of a nodeSequence conforming to these conditions should therefore return an LLNode<L>?.
Therefore the statement self.rootNode = nodeSequence.firstNode should be completely possible, since self.rootNode is of type LLNode<L>?.
When I try to compile the code, I get the error:
<stdin>:501:33: error: extra argument 'nodeSequence' in call
let nodes = LLLinkedList(nodeSequence: nodeSequence)
501:33 refers to the first statement of the append method.
How can there be an extra argument 'nodeSequence' if I defined the initializer with an argument called nodeSequence?
Here's a code sample containing only the parts relevant to the question:
SwiftStub-Code

The problem is that your append function is not mandating the type of LLNodeType.T for the sequence you're appending:
func append<NT: LLNodeType>(nodeSequence: NT) {
// here, nodeSequence could be of a String, an Int, who knows...
let nodes = LLLinkedList(nodeSequence: nodeSequence)
...
// but here you try to append it to the existing list...
// what if it's not a list of the same type?
if self.isEmpty {
self.rootNode = nodes.rootNode
} else {
/// appends a node by pointing the last nodes pointer ('nextNode' property) to the new node
self.lastNode!.nextNode = nodes.rootNode
}
}
You can resolve this by mandating you only append sequences of the same type as your
func append<NT: LLNodeType where NT.T == L>(nodeSequence: NT) {
// etc...

Related

swift convenience init and generic class

I have a problem creating a convenience init method that then calls a designated init on a class with generic type parameters. Here is the swift 3.1 XCode Version 8.3.2 (8E2002) playground
protocol A {
var items: [String] { get set }
func doSomething()
}
struct Section : A {
var items: [String] = []
func doSomething() {
print("doSomething")
items.forEach { print($0) }
}
}
class DataSource<T: A> {
var sections: [T]
init(sections: [T]) {
self.sections = sections
}
func process() {
sections.forEach { $0.doSomething() }
}
convenience init() {
var section = Section()
section.items.append("Goodbye")
section.items.append("Swift")
self.init(sections: [section])
}
}
/*: Client */
var section = Section()
section.items.append("Hello")
section.items.append("Swift")
let ds = DataSource(sections: [section])
ds.process()
If no convenience init exists, then the code beneath the /*: Client */ section compiles and executes without issue. If I add in the convenience init I get the following compilation error:
cannot convert value of type '[Section]' to expected argument type '[_]'
self.init(sections: [section])
I wouldn't think that this would be an issue since in the convenience init I am creating a Section struct which implements the protocol A which satisfies the generic constraint on the DataSource class. The convenience init is performing the same operations as the client code, yet it is unable to convert a [Section] into a [A]. Is this an initialization sequencing issue?
Generic placeholders are satisfied at the usage of the given generic type – therefore inside your convenience init, you cannot assume that T is a Section. It's an arbitrary concrete type that conforms to A.
For example, it would be perfectly legal for the caller to define a
struct SomeOtherSection : A {...}
and then call your convenience initialiser with T being SomeOtherSection.
The solution in this case is simple, you can just add your convenience initialiser in an extension of DataSource, where T is constrained to being Section – therefore allowing you to call init(sections:) with a [Section]:
extension DataSource where T == Section {
convenience init() {
var section = Section()
section.items.append("Goodbye")
section.items.append("Swift")
self.init(sections: [section])
}
}
// ...
// compiler will infer that T == Section here.
let ds = DataSource()

How to call a static method on a base class from a generic class<T>?

I'm trying to call a static method in a base class from a generic subclass. See simplified playground code below.
Calling the non-static 'dump' function works.
The similar static call fails. Various attempts to typecast the array also fail.
In the full production code, "ClassA" is in a 3rd-party module and can't be changed. It can be further subclassed and extended.
Does Swift offer some magical typecast to have ClassT call ClassA.dump() directly?
class ClassT<T> {
var dict=[String:T]()
func add(key:String, obj:T) {
dict[key]=obj
let arr=Array(dict.values)
dump(arr) // works -> but not as expected, see comment below !!!
ClassA.dump(arr) // error: cannot convert value of type 'Array<T>' to expected argument type '[ClassA]'
ClassA.dump(arr as! [ClassA]) // error: cannot convert value of type 'Array<T>' to type '[ClassA]' in coercion
ClassA.dump(arr as! [AnyObject]) // error: 'AnyObject' is not a subtype of 'T'
ClassA.dump(arr as! [Any]) // error: 'Any' is not a subtype of 'T'
}
}
class ClassA {
func dump(arr:[ClassA]) {
ClassA.dump(arr)
}
static func dump(arr:[ClassA]) {
print(arr)
}
}
class ClassB:ClassA {
static let o=ClassT<ClassA>()
func test() {
ClassB.o.add("Elem1", obj:self)
}
}
You have to add a constraint to specify that T derives from ClassA.
class ClassT<T: ClassA> {
var dict = [String : T]()
func add(key: String, obj: T) {
dict[key] = obj
let arr = Array(dict.values) //probably unecessary
dump(arr) // works
ClassA.dump(arr)
}
//...
Without it, the compiler has no way to enforce that all conforming types T will be castable to ClassA.

Generic constrained type default value

Consider the following code:
protocol JSONParserType {
associatedtype Element
}
// MARK: - Entities
struct Item {}
// MARK: - Parsers
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init(innerParser: T = InnerParser()) {
self.innerParser = innerParser
}
}
struct InnerParser: JSONParserType {
typealias Element = Item
}
The OuterParser has a child parser that should be constrained to a specific type. Unfortunately providing a default value in the initializer (or in the property definition itself) does lead to the compiler throwing a "Default argument value of type 'InnerParser' cannot be converted to type 'T'".
If I remove the default value assignment and just instantiate the OuterParser providing the InnerParser explicitly, everything is fine.
let outerParser = OuterParser(innerParser: InnerParser())
My question is what's the reason that the approach providing a default value that actually meets the constraints does not work.
The problem is that the actual type of T isn't defined by the class – it's defined by the code that uses the class. It will therefore be defined before you do anything in your class (at either instance or static level). You therefore can't assign InnerParser to T, as T has already been defined to be a given type by that point, which may well not be InnerParser.
For example, let's consider that you have another parser struct:
struct AnotherParser: JSONParserType {
typealias Element = Item
}
and let's assume that your current code compiles. Now consider what would happen when you do this:
let parser = OuterParser<AnotherParser>()
You've defined the generic type to be AnotherParser – but the initialiser will try to assign InnerParser to your property (now of type AnotherParser). These types don't match, therefore it cannot possibly work.
Following the same logic, this implementation also won't work:
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init() {
self.innerParser = InnerParser()
}
init(innerParser: T) {
self.innerParser = innerParser
}
}
As there's no guarantee that the generic type T will be the same type as InnerParser. Sure, you can force downcast to T – but that'll just make you code crash if the types aren't compatible.
Unfortunately, there's no real clean solution to this problem. I think the best your best option is probably to create two factory methods for creating your OuterParser instance.
enum Parser {
static func createParser() -> OuterParser<InnerParser> {
return OuterParser(innerParser:InnerParser())
}
static func createParser<T>(innerParser:T) -> OuterParser<T> {
return OuterParser(innerParser:innerParser)
}
}
let innerParser = Parser.createParser() // OuterParser<InnerParser>
let anotherParser = Parser.createParser(AnotherParser()) // OuterParser<AnotherParser>
We're using an caseless enum here to avoid polluting the global namespace with extra functions.
Although this isn't very Swifty, and for that reason I would also recommend maybe rethinking your logic for how you define your parsers.
type T more like a child protocol of JSONParserType you can convert it:
init(innerParser: T = InnerParser() as! T) {
self.innerParser = innerParser
}

Swift generic parameter cannot be bound to non-#objc protocol

The following code produces a compile error of "Generic parameter "T" cannot be bound to non-#objc protocol type 'AAA' on the fail line. When I use a class instead of a protocol, it works ok. Also, if I add an #objc to the protocol it also works, but only in 6.4 beta. Any suggestions would be helpful.
protocol AAA {
var id: String { get set }
}
class BBB: AAA {
var id: String = ""
}
class BBBService {
func getAll<T:AAA>() -> [T] {
var returnArray:[T] = [T]()
return returnArray
}
}
class TestIt
{
func myTest() {
var service = BBBService()
var fail:[AAA] = service.getAll() // fails with Generic parameter "T" cannot be bound to non-#objc protocol type AAA
var succeed:[BBB] = service.getAll()
}
}
this also fails:
<T where T:AAA>
Update - from a practical perspective, adding the #objc causes other problems in my app. So, that is not an option at this point and time.
The trouble is with this line:
getAll<T: AAA>() -> [T]
you are declaring that T must be a concrete type that implements the protocol AAA. It’s important to distinguish between the protocol AAA, i.e. code like this:
func getAll() -> [AAA] {
var returnArray: [AAA] = [BBB()]
return returnArray
}
which works fine (returns an array of references to AAA-conforming objects, which could be of type BBB, or type CCC), and this:
func getAll<T: AAA>() -> [T] {
var returnArray: [T] = [] // what is T? No idea.
return returnArray
}
in which you are saying to the compiler “write me a version of getAll, in which T can be replaced by any specific type that implements AAA”.
This is why your second version compiles - you’re fixing T to be the actual type BBB.
Bear in mind, T might be a struct. In which case the array returned must be sized specifically for whatever struct is being held, right there as a value within the array. As opposed to if the protocol was #objc in which case it would at least be known to only be a class reference of a fixed size.
If what you actually want is an array of protocols, you should remove the generic placeholder and just return an array of protocols:
func getAll() -> [AAA] {
var returnArray: [AAA] = []
return returnArray
}

How to specify multiple inter-dependent associated types in swift protocols?

How do I specify multiple associated types in a protocol with added dependencies on their inner associated types? For example:
protocol CombinedType
{
typealias First: FirstType
typealias Second: SecondType
var first: First { get }
var second: Second { get }
}
protocol FirstType
{
typealias Value
var value: Value { get }
}
protocol SecondType
{
type alias Value
var value: Value { get }
}
I need to specify a limitation on the CombinedType protocol that would make it so FirstType.Value == SecondType.Value much like in a where clause in generics.
Without that limitation, the code:
func sum<Combined: CombinedType>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
produces an error Combined.FirstType.Value is not compatible with Combined.SecondType.Value.
But if I explicitly add the where clause to the function, it compiles:
func sum<Combined: CombinedType where Combined.FirstType.Value == Combined.SecondType.Value>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
The problem is that this where needs to be copied everywhere, which produces a ton of boilerplate and it gets even worse when using CombinedType inside another protocol, in which case the where clause gets larger and larger.
My question is: can I somehow include that limitation in the protocol itself?