Hello, I'm trying to do something that I think should be simple but I can't figure it out.
Basically I'm trying to make my class respond to a String input and assume a certain associated type for all future operations. I could do this with subclasses and a factory pattern but that seems clunky given Swift's more modern features.
I have tried a few different iterations of giving the Datafile a generic parameter as in class Datafile<DataType: FixedWidthInteger> {} or similar (class Datafile<DataType> where DataType: FixedWidthInteger) but I keep getting stuck with that too.
Does anyone have an idea whether this is possible? Here's the essential code I'm working with:
class Datafile {
fileprivate let CurrentDataType: DataType.Type
let activePoints: [Bool]
init?(filepath: String) {
if let CurrentDataType = DataType(from: filepath) {
self.CurrentDataType = CurrentDataType
} else {
return nil
}
}
// ...
getDataPoints() -> [Int16] {
let pointSize = MemoryLayout<CurrentDataType>.size // Swift doesn't agree this is a Type
// ...
var tempBufferArray = [CurrentDataType](repeating: 0, count: 128) // not possible
fread(&tempBufferArray, size, numberOfDataPoints, inputFile)
}
}
The closest I've got is defining a protocol
protocol DataReader {
associatedtype DataType where DataType: FixedWidthInteger
}
and making Datafile conform to it. But then I'm stuck defining multiple types again, each with their own DataType typealias.
Although you can't use P.Type variables as types in Swift directly, one thing you can do instead is put an extension on the protocol P. Within the extension, the type Self refers to the dynamic type of the value, so you can try this:
extension DataType {
static func _readDataPoints() -> [Int16] {
let pointSize = MemoryLayout<Self>.size
...
var tempBufferArray = [Self](repeating: 0, count: 128)
fread(&tempBufferArray, size, numberOfDataPoints, inputFile)
...
return result
}
}
class Datafile {
let CurrentDataType: DataType.Type
func getDataPoints() -> [Int16] {
return CurrentDataType._readDataPoints()
}
}
I tried this but I keep getting stuck with defining DataType as anything useful: it has to be a FixedWidthInteger to be meaningful. More helpfully, it must be initiable via Int(value), have basic arithmetic capabilities (support basic math operators) must have .max and .min. I can't seem to define the type in any way that will give me these capabilities without a Self requirement.
Either I try to make define DataType as conforming to FixedWidthInteger, in which case I can't return -> DataType or DataType.Type (Protocol 'DataType' can only be used as a generic constraint because it has Self or associated type requirements). Or I don't define it as a FixedWidthInteger and then the actual readDataPoints function is extremely limited in its capabilities.
Any ideas?
Edit: I managed to get the code as simple as the following, which is much more satisfying than before.
let fileExtension = filepath.suffix(3)
switch fileExtension {
case "ui8":
print(UInt8.getAllData(from: datafile))
case "si8":
print(Int8.getAllData(from: datafile))
case "i16":
print(Int16.getAllData(from: datafile))
default:
fatalError("Unsupported File Type: \(fileExtension)")
}
It still feels weird to have to perform the call three separate times. Is there no way to return, say, a FixedWidthInteger and just do the call once? At the moment the code isn't doing much but I have concerns that this won't be very extensible or maintainable as written..
let type = getType(fileExtension)
type.getAllData(...) // is this ever possible if type has Self requirements?
func getType(_ fileExtension: String) -> FixedWidthInteger {
switch fileExtension {
case "si8": return Int8.self
case "ui8": return UInt8.self
case "i16": return Int16.self
}
}