API design: Overload on return type or specify type as argument in a deserialization API?

Hi,

I am designing a new deserialization API. Looking for advice on return type overloading vs. specifying the decode-as type in a method argument.

Looking at the Decodable APIs in Foundation the decode methods, such as in SingleValueDecodingContainer, has the type as an explicit argument, for example:

func decode(_ type: String.Type) throws -> String
func decode(_ type: Int.Type) throws -> Int
func decode<T>(_ type: T.Type) throws -> T where T : Decodable

Rather than overloading only on the return type:

func decode() throws -> String
func decode() throws -> Int
func decode<T>() throws -> T where T : Decodable

Considering that one common use case is decoding to properties in an init method, the former Foundation Decodable-like design requires repeating the property type in each decode call as follows:

struct SomeFoo {
  let a: Int   // Int
  let b: String 
  init(_ decoder: Decoder) throw {
     a = try decoder.decode(Int.self)    // Yes, again, it's an Int
     b = try decoder.decode(String.self)
  } 
}

While the argument-less version would simply be:

struct SomeFoo {
  let a: Int
  let b: String 

  init(_ decoder: Decoder) {
     a = try decoder.decode()
     b = try decoder.decode()
  } 
}

Apart from type-checker performance (I remember this being a concern earlier in Swift's history, is it still relevant in this scenario?), are there any good reasons to have the type as an explicit argument?

You have a nice dilemma. How about bypassing it completely and supporting both usages?

func f<T: Numeric>(type: T.Type = T.self) -> T {
    .zero
}
let a: Int = f()
let b = f(type: Int.self)
6 Likes

@cukr's choice is the best pattern for inference, because it allows passing an explicit type when the inferred type isn't clear. However, there are reasons Decoder and UnsafePointer take type arguments explicitly: in both cases it keeps you from accidentally using the wrong type. These are APIs where the format of a value matters, and so mixing up two similar types like Int and Int32 might cause real problems.

5 Likes