1
votes

In Swift, how do you define an array of generics with type conforming Equatable?

Example:

struct File<T: Equatable> {
    public var lines: [T]
    private var lineCursor = 0
    public var currentLine: T {
        get { return lines[lineCursor] }
        set { lineCursor = lines.index(where: { $0 == newValue }) ?? 0 }
    }
}

struct Folder {
    public var files: [File]? // compile time error
}

→ Reference to generic type 'File' requires arguments in <…>

…so far I tried:

[File<Any>]

→ Type 'Any' does not conform to protocol 'Equatable'


[File<Any: Equatable>]

→ Consecutive declarations on a line must be separated by ';'


[File<Any, Equatable>]

→ Generic type 'File' specialized with too many type parameters (got 2, but expected 1)


[File<Any & Equatable>]

→ Using 'Equatable' as a concrete type conforming to protocol 'Equatable' is not supported


[File<(Any: Equatable)>]

→ Cannot create a single-element tuple with an element label


[File<(Any, Equatable)>]

→ Type '(Any, Equatable)' does not conform to protocol 'Equatable'`


[File<(Any & Equatable)>]

→ Using 'Equatable' as a concrete type conforming to protocol 'Equatable' is not supported


[File<[Any: Equatable]>]

→ 'File' requires that 'Equatable' conform to 'Equatable'


[File<[Any, Equatable]>]

→ Consecutive declarations on a line must be separated by ';'


[File<[Any & Equatable]>]

→ 'File' requires that 'Equatable' conform to 'Equatable'


What is the correct syntax?


[EDIT] simplified the example


[EDIT] updated example:

class File<T: Equatable> {
    var lines = [T]()
    var lineCursor: Int = 0
    var currentLine: T {
        get { return lines[lineCursor] }
        set { lineCursor = lines.index(where: { $0 == newValue }) ?? 0 }
    }
    var visible = true
}

class Folder {
    var files = [File]() // Generic parameter 'Type' could not be inferred; I want this to be a mixed array
    func currentLinesFromVisibleFiles() -> String {
        return files.filter({ $0.visible }).map({ String(describing: $0.currentLine) }).joined(separator: "/")
    }
}

var stringFile = File<String>()
stringFile.lines = ["strong", "string", "a", "b", "c"]
stringFile.currentLine = "string"
stringFile.visible = true

var intFile = File<Int>()
intFile.lines = [6, 12, 0, 489]
intFile.currentLine = 489
intFile.visible = true

var doubleFile = File<Double>()
doubleFile.lines = [92.12, 4.9753, 1.6]
doubleFile.currentLine = 92.12
doubleFile.visible = false

var boolFile = File<Bool>()
boolFile.lines = [true, false]
boolFile.currentLine = true
boolFile.visible = true

var folder = Folder()
folder.files = [stringFile, intFile, doubleFile, boolFile]

let output = folder.currentLinesFromVisibleFiles() // I want: "string/489/true"
1
What type of values do you expect to use with this Data and File class? String? Int? That's what you need to use, not Any.rmaddy
What's the point of that Data type? I don't really see a point in defining a type with a single instance property with a non-restricted generic type. Why don't you simply use public var lines: [Type] in your File type? Moreover, copying existing built-in type names is a bad idea as it will most probably lead to confusion for the readers of your code.Dávid Pásztor
@rmaddy: I’d like to use any type that is or will be available. Like Float and Bool and also any future custom types that are Equatable.Rudy Phillipps
Fine but when you instantiate File or Data you need to do so with a specific (and Equatable) type. Any isn't Equatable.rmaddy
@DávidPásztor: thank you for your comment. That code is just an example to help to understand what I want to learn. I guess by "copying existing built-in type names" you mean Type?Rudy Phillipps

1 Answers

1
votes

You have to specify the type of T parameter on [File<T>]? to let the compiler pass, note that you're trying to create an homogeneous array that once you specify T as the final type you can't mix types on file, i.e you can't mix File<Int> and File<String>. if you're requiring the Equatable conformance in order to calculate currentLine you can use conditional conforming to add the property dynamically weather or not T is equatable, as:

protocol HasCurrentLine{
    associatedtype LineType
    var currentLine: LineType { set get }
}

struct File<T> where T:Any {
    public var lines: [T]
    var lineCursor = 0
}

extension File : HasCurrentLine where T : Equatable{
    typealias LineType = T
    var currentLine: T {
        get { return lines[lineCursor] }
        set { lineCursor = lines.index(where: { $0 == newValue }) ?? 0 
    }
  }
}
struct Folder {
    public var files: [File<Any>]?
}

this way you can count lines whenever T is Equatable