JetForMe
(Rick M)
1
Turning a case let with varying associated value types into a function call
Parsing a TIFF file, I've got a structure like this:
struct
DirectoryEntry
{
enum
Value
{
case signedInt([Int64])
case unsignedInt([UInt64])
case rational([Rational])
case srational([SRational])
case double([Double])
}
let count : UInt64
let values : Value?
}
Each DirectoryEntry (or tag) in a TIFF file can have one or more values of a specified
type, represented by the enumeration above (Rationa1 and SRational are structs
that hold two integers).
When I process the various tags, I have an expansive switch statement:
let de = readDirectoryEntry()
switch (de.tag)
{
...
case .compression:
guard
case let .unsignedInt(values) = de.values,
de.count == 1
else
{
throw Error.invalidTIFFFormat
}
ifd.compression = try CompressionType.compression(fromVal: values.first!)
...
}
I'd like to reduce the boilerplate code to validate the de with something like this:
case .compression:
let values = try de.validate(type: .unsignedInt, count: 1)
ifd.compression = try CompressionType.compression(fromVal: values.first!)
Such that values is of the appropriate associated type, depending on which specific
type it is. In the above example, it would be [UInt64]. I’m not sure how to express
that in Swift, though. Is there a way to specify the type of T based on what type: is?
I don't know the form of CompressionType.compression, but in some case it would be possible.
I assume this function has following form. If it's not, it would be difficult...
struct CompressionType{
static func compression<T>(fromVal: T) throws -> CompressionType {
}
}
- make the protocol
MyInteger.
protocol MyInteger{}
extension Int64: MyInteger{}
extension UInt64: MyInteger{}
extension Rational: MyInteger{}
extension SRational: MyInteger{}
extension Double: MyInteger{}
- make the
validate that returns [MyInteger]. You can write like this.
extension DirectoryEntry.Value: Equatable {}
extension DirectoryEntry{
func validate<T: MyInteger>(type: ([T]) -> Value, count: UInt64) throws -> [MyInteger] {
guard let values = self.values else {
throw Error.invalidTIFFFormat
}
guard self.count == 1 else {
throw Error.invalidTIFFFormat
}
switch values{
case let .signedInt(myIntegerValues as [MyInteger]),
let .unsignedInt(myIntegerValues as [MyInteger]),
let .rational(myIntegerValues as [MyInteger]),
let .srational(myIntegerValues as [MyInteger]),
let .double(myIntegerValues as [MyInteger]):
if let castedValues = myIntegerValues as? [T], type(castedValues) == values{
return myIntegerValues
}
default:
throw Error.invalidTIFFFormat
}
}
}
- extend
MyInteger to use CompressionType.compression that is a generic function.
extension MyInteger{
func compression() throws {
try CompressionType.compression(fromVal: self)
}
}
- you can write like this. It's sad that
DirectoryEntry.Value cannot be omitted.
case .compression:
let values = try de.validate(type: DirectoryEntry.Value.unsignedInt, count: 1)
ifd.compression = try values.first!.compression()
2 Likes