1

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"
  • 2
    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 May 13 '18 at 18:06
  • 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 May 13 '18 at 18:10
  • @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 May 13 '18 at 18:11
  • Fine but when you instantiate `File` or `Data` you need to do so with a specific (and Equatable) type. `Any` isn't `Equatable`. – rmaddy May 13 '18 at 18:13
  • @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 May 13 '18 at 18:15
  • @rmaddy: so instantiating with a wildcard Equatable is not possible? I mean a wildcard like Any is for any type. A wildcard for any type conforming Equatable. – Rudy Phillipps May 13 '18 at 18:17
  • It would really help if you updated your question with a clear example of how you intend to use these `File` and `Folder` classes. – rmaddy May 13 '18 at 18:22
  • @RudyPhillipps no, I meant `Data`. The issue with your current approach is that there's no type `File` without telling the compiler which type are you wrapping in `File` as its generic type. Just like there's no `Optional` or `Array` type without their generic type parameters. What you are essentially seeking is `File`, but that also can't work since you cannot use `Equatable` as a concrete type. Without knowing exactly what it is you're trying to achieve, one possible solution could be creating a type-erased `Equatable` and storing an array of that in `Folder`. – Dávid Pásztor May 13 '18 at 18:33

1 Answers1

1

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

Jans
  • 11,064
  • 3
  • 37
  • 45
  • Thanks a lot for your answer. Following this example I ran into an issue when trying to save a File() to the files array: `Cannot convert value of type 'File' to expected element type 'File'`. I updated the example in my question to reduce misunderstandings. – Rudy Phillipps May 13 '18 at 21:09