Optionally optional metatypes

Hi all! I'm relatively new to Swift, coming from a primarily Objective-C background. I'm looking to write a parsing library in what I think might be a nicely typed way. I'm wondering if I'm thinking about this problem in the correct way.

Essentially, the task I'm trying to solve is to parse a stream of text containing keys associated with optional values. The parser declaration would allow for the adopter to provide a metatype conforming to LosslessStringConvertible that should be parsed from the text stream. The associated values may be required or optional. I'd like to be able to encode this in the metatype argument, but I'm not exactly sure how.

So for example, this may be used as follows:

parseValue(forKey: "key1", type: String?.self)  // A string value may optionally be available in the input stream
parseValue(forKey: "key2", type: String.self)   // A string value is required from the input stream

A couple options for a function declaration:

// Requires an optional metatype. Passing String.self fails
func parseValue<T: LosslessStringConvertible>(forKey: String, type: T?.Type?) {}
// Requires a non-optional metatype. Passing String?.self fails
func parseValue<T: LosslessStringConvertible>(forKey: String, type: T.Type?) {}

Is there a way I can declare a function that allows either an optional or a non-optional LosslessStringConvertible metatype?

Thanks!

My first instinct is that it'd fit better to do a Codable style function signature.

// Non-optional T
func parseValue<T>(forKey: String, type: T.Type) where T: LosslessStringConvertible { ... }
// Optional T
func parseValueIfExist<T>(forKey: String, type: T.Type) where T: LosslessStringConvertible { ... }

Should you really need to put information in type, you could separate function into 2 overloads. Swift will choose the most specific one

func test<T>(_ value: T?.Type) where T: LosslessStringConvertible {
  print("Optional", T.self)
}

func test<T>(_ value: T.Type) where T: LosslessStringConvertible {
  print("Non-optional", T.self)
}

test(Int?.self) // "Optional Int"
test(String.self) // "Non-optional String"

Thanks! That makes sense. But I'm still wondering if there might be cases where it'd be useful to encode an optionally optional metatype like this.

The way I really wanted to approach to this was to encode the parsable definition into a sequence of structs similar to the following:

struct ParseField<T: LosslessStringConvertible> {
    let key: String
    let valueType: T?.Type?
}

Since I'd be storing the metatype in an ivar, having two overloaded initializers wouldn't quite work here since they'd still need to be assigned to this shared ivar.

In retrospect, I'm thinking this isn't how metatypes should be used. Instead I've defined my own enum that encodes this more customized notion of optionality.

I'd love to have folks more familiar with Swift critique this approach. Is this better? Or am I replicating Swift optionals unnecessarily?

enum ExpectedValue {
    case none
    case optional(LosslessStringConvertible.Type)
    case required(LosslessStringConvertible.Type)
}
struct ParseField {
    let key: String
    let value: ExpectedValue
}
let a = ParseField(key: "key1", value: .none)
let b = ParseField(key: "key2", value: .optional(String.self))
let c = ParseField(key: "key3", value: .required(Int.self))

Personally, I'd prefer to have isRequired as a separate variable

struct ParseField {
    var key: String
    var valueType: LosslessStringConvertible.Type
    var isRequired: Bool
}

In any case, since you seem to be doing some deserialization, have you looked into Codable? Does it not work in your case?
You could also try to implement a new Encoder/Decoder.
It could be somewhat tedious, but it'd work well for general structure, and make it very simple for user.

Thanks for the tip. I'll definitely be looking into that :slight_smile:

Here a link for how the user should use it.
Here is some of my Encoder/Decoder implementation a while back. I didn't add much comment though :stuck_out_tongue:.

1 Like