Turning a case let with varying associated value types into a function call

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 {
    }
}
  1. make the protocol MyInteger.
protocol MyInteger{}
extension Int64: MyInteger{}
extension UInt64: MyInteger{}
extension Rational: MyInteger{}
extension SRational: MyInteger{}
extension Double: MyInteger{}
  1. 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
        }
    }
}
  1. extend MyInteger to use CompressionType.compression that is a generic function.
extension MyInteger{
    func compression() throws {
        try CompressionType.compression(fromVal: self)
    }
}
  1. 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
Terms of Service

Privacy Policy

Cookie Policy