I'm trying to write a generic function to parse several different data types.
Originally this method only worked for Codable types, so its generic type was constrained with <T: Codable> and all was well. Now though, I am attempting to expand it to check if the return type is Codable, and parse the data accordingly based on that check
func parse<T>(from data: Data) throws -> T? {
switch T.self {
case is Codable:
// convince the compiler that T is Codable
return try? JSONDecoder().decode(T.self, from: data)
case is [String: Any].Type:
return try JSONSerialization.jsonObject(with: data, options: []) as? T
default:
return nil
}
}
So you can see that the type-checking works fine, but I'm stuck on getting JSONDecoder().decode(:) to accept T as a Codable type once I've checked that it is. The above code doesn't compile, with the errors
Cannot convert value of type 'T' (generic parameter of instance method 'parse(from:)') to expected argument type 'T' (generic parameter of instance method 'decode(_:from:)')
and
In argument type 'T.Type', 'T' does not conform to expected type 'Decodable'
I've tried a number of casting techniques, like let decodableT: <T & Decodable> = T.self etc., but all have failed-- usually based on the fact that Decodable is a protocol and T is a concrete type.
Is it possible to (conditionally) reintroduce protocol conformance to an erased type like this? I'd appreciate any ideas you have, either for solving this approach or for similar generic-parsing approaches that might be more successful here.
EDIT: A Complication
#vadian helpfully suggested creating two parse(:) methods with different type constraints, to handle all cases with one signature. That's a great solution in many cases, and if you're stumbling across this question later it may very well solve your conundrum.
Unfortunately, in this only works if the type is known at the time that parse(:) is invoked-- and in my application this method is called by another generic method, meaning that the type is already erased and the compiler can't pick a correctly-constrained parse(:) implementation.
So, to clarify the crux of this question: is it possible to conditionally/optionally add type information (e.g. protocol conformance) back to an erased type? In other words, once a type has become <T> is there any way to cast it to <T: Decodable>?
You can conditionally cast T.self to Decodable.Type in order to get a metatype that describes an underlying Decodable conforming type:
switch T.self {
case let decodableType as Decodable.Type:
However, if we try to pass decodableType to JSONDecoder, we have a problem:
// error: Cannot invoke 'decode' with an argument list of type
// '(Decodable.Type, from: Data)'
return try? JSONDecoder().decode(decodableType, from: data)
This doesn't work because decode(_:from:) has a generic placeholder T : Decodable:
func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T
and we cannot satisfy T.Type with Decodable.Type because Decodable doesn't conform to itself.
As explored in the above linked Q&A, one workaround for this issue is to open the Decodable.Type value in order to dig out the underlying concrete type that conforms to Decodable – which we can then use to satisfy T.
This can be done with a protocol extension:
extension Decodable {
static func openedJSONDecode(
using decoder: JSONDecoder, from data: Data
) throws -> Self {
return try decoder.decode(self, from: data)
}
}
Which we can then call as:
return try? (decodableType.openedJSONDecode(using: JSONDecoder(), from: data) as! T)
You'll note that we've had to insert a force cast to T. This is due to the fact that by casting T.self to Decodable.Type we erased the fact that the metatype also describes the type T. Therefore we need to force cast in order to get this information back.
All in all, you'll want your function to look something like this:
func jsonDecode<T>(_ metatype: T.Type, from data: Data) throws -> T {
switch metatype {
case let codableType as Decodable.Type:
let decoded = try codableType.openedJSONDecode(
using: JSONDecoder(), from: data
)
// The force cast `as! T` is guaranteed to always succeed due to the fact
// that we're decoding using `metatype: T.Type`. The cast to
// `Decodable.Type` unfortunately erases this information.
return decoded as! T
case is [String: Any].Type, is [Any].Type:
let rawDecoded = try JSONSerialization.jsonObject(with: data, options: [])
guard let decoded = rawDecoded as? T else {
throw DecodingError.typeMismatch(
type(of: rawDecoded), .init(codingPath: [], debugDescription: """
Expected to decode \(metatype), found \(type(of: rawDecoded)) instead.
""")
)
}
return decoded
default:
fatalError("\(metatype) is not Decodable nor [String: Any] nor [Any]")
}
}
I have made a couple of other changes:
Changed the return type from T? to T. In general, you either want to handle errors by having the function return an optional or by having it throw – it can be quite confusing for the caller to handle both.
Added an explicit parameter for T.Type. This avoids relying on the caller to use return type inference in order to satisfy T, which IMO is a similar pattern to overloading by return type which is discouraged by the API design guidelines.
Made the default: case fatalError as it should probably be a programmer error to supply a non-decodable type.
Related
There are a lot of answers surrounding my issue, but the solutions I have tried for them have not quite solved the problem. I'm not sure if it has something to do with having multiple generics or something else (also newer to Swift so still wrapping my head around syntax).
I noticed a lot of commonality in my API request code so I decided to abstract the main work out and have it work with any codable request/response objects. Here is my main request method:
private func sendRequest<T: Encodable, U: Decodable>(url: URL, requestModel: T) -> Promise<U>
And I am trying to call it as such:
public func signIn(requestModel: SignInRequest) -> Promise<SignInResponse> {
let url = URL(string: authURL + "/signin")!
//getting compile error "Cannot explicitly specialize generic function" on this line
return sendRequest<SignInRequest, SignInResponse>(url: url, requestModel: requestModel)
}
I've tried assigning directly to the return object:
let x : Promise<SignInResponse> = sendRequest(...)
return x
to help the compiler out (as suggested in other solutions) but still same issue. Any insights?
I ended up using a solution based on this post: https://stackoverflow.com/a/36232002/1088099
I explicitly passed in the request and response object types as parameters:
private func sendRequest<T: Encodable, U: Decodable>(requestModelType: T.Type, responseModelType: U.Type, url: URL, requestModel: T) -> Promise<U>
and calling:
public func signIn(requestModel: SignInRequest) -> Promise<SignInResponse> {
let url = URL(string: authURL + "/signin")!
return sendRequest(requestModelType:SignInRequest.self,
responseModelType:SignInResponse.self,
url: url,
requestModel: requestModel)
}
Not as clean of a solution I was hoping for, but works.
Inference will succeed if you give enough context with the function's arguments to determine all generic parameters.
From you code, I suspect this is not the case as neither does the generic type U neither appear as an argument, nor there is an additional type constraint to specify it. Therefore, the compiler can't infer what is the concrete type of your function's return value from a function call.
Hence, if there's a relationship between T and U, you may use it as an additional constraint to "help" the compiler infer U once it found T. Here is a minimal example:
protocol Foo {
associatedtype Bar
var bar: Bar { get }
}
struct FooImpl: Foo {
let bar: Int = 0
}
func f<T, U>(foo: T) -> U where T: Foo, U == T.Bar {
return foo.bar
}
let a = f(foo: FooImpl())
Protocol Foo has an associated type Bar. By using this association in the function f's signature to add a constraint on its return type, Swift's compiler is now able to fully infer the specialized signature of f when I call it with an instance of FooImpl.
In your particular example, I would suggest writing a protocol to which the requestModel parameter should confirm, with an associated type to place a constraint on the return value.
For instance:
protocol RequestModel: Encodable {
associatedtype ResponseModel: Decodable
}
struct AnAwesomeModel: RequestModel {
typealias ResponseModel = String
}
private func sendRequest<T: RequestModel, U>(url: URL, requestModel: T) -> Promise<U>
where U == T.ResponseModel
{
// ...
}
// x's type will be inferred as String
let x = sendRequest(url: URL("http://...")!, requestModel: AnAwesomeModel())
What I want to accomplish is to pass the dynamic type of object as a parameter to generic function. I'm able to see the correct type I want with type(of:), but I'm unable to pass it as parameter, because in generic function I'm still getting the static type.
Here is an example what I want to do:
protocol SomeUsefulProtocol {}
struct MyType1: SomeUsefulProtocol {}
struct MyType2: SomeUsefulProtocol {}
let objects: [SomeUsefulProtocol] = [MyType1(), MyType2()]
let someClosureToWorkWithMyType: Any = { (object: MyType1) in
//Do some great things
}
func someMethod<T>(type: T.Type, object: Any) {
typealias ClosureType = (T) -> Void
if let closure = someClosureToWorkWithMyType as? ClosureType, let object = object as? T {
closure(object)
}
}
for object in objects {
someMethod(type: type(of: object), object: object)
}
Here i want closure someClosureToWorkWithMyType to be called when object has a type 'MyType1', but inside the method the type of object is the static type (SomeUsefulProtocol).
The problem here is that object as declared as an instance of the protocol SomeUsefulProtocol. An instance of a protocol is not any specific concrete type, and can be one of potentially many concrete types.
Swift's generics system requires known concrete types at compile time to create the right specializations. There are a number of blog posts and StackOverflow questions and answers which delve deeper into the trickiness (and more often than not, the futility)of trying to use Swift protocols and Swift generics together.
In your specific example, your method can be made to work in one of two ways: either don't declare object as a protocol instance, but instead make it a concrete type, e.g.
let object: MyType = MyType()
Or keep it declared as-is, but cast it to a concrete type when passing it into your function, e.g.
(object as? MyType).flatMap{ someMethod(type: type(of: $0), object: $0) }
I was fiddling around with generic types in the Swift playgrounds, when I attempted to use the code below:
class Node<T> {
let value: T
init(_ t: T) {
self.value = t
}
func convert<U>(to type: U.Type) -> Node<U>? {
guard let new = value as? U else {
return nil
}
return Node(new)
}
}
Note: This code is hypothetical and is not meant to do anything in the real world.
On line 12, where I return the new Node from Node.convert(_:), I get this error message:
U' is not convertible to 'T'
Now this seems rather odd to me. In theory, since we can create a Node with any type, shouldn't we be able to use any generic type initialize it? Does this have something to do with the type inference not being able to infer the type?
I would like to create a protocol like the following:
protocol Parser {
func parse() -> ParserOutcome<?>
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser)
}
I want to have parsers that return either a result of a specific type, or another parser.
If I use an associated type on Parser, then I can't use Parser in the enum. If I specify a generic type on the parse() function, then I can't define it in the implementation without a generic type.
How can I achieve this?
Using generics, I could write something like this:
class Parser<Result> {
func parse() -> ParserOutcome<Result> { ... }
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser<Result>)
}
This way, a Parser would be parameterized by the result type. parse() can return a result of the Result type, or any kind of parser that would output either a result of the Result type, or another parser parameterized by the same Result type.
With associated types however, as far as I can tell, I'll always have a Self constraint:
protocol Parser {
associatedtype Result
func parse() -> ParserOutcome<Result, Self>
}
enum ParserOutcome<Result, P: Parser where P.Result == Result> {
case result(Result)
case parser(P)
}
In this case, I can't have any type of parser that would return the same Result type anymore, it has to be the same type of parser.
I would like to obtain the same behavior with the Parser protocol as I would with a generic definition, and I would like to be able to do that within the bounds of the type system, without introducing new boxed types, just like I can with a normal generic definition.
It seems to me that defining associatedtype OutcomeParser: Parser inside the Parser protocol, then returning an enum parameterized by that type would solve the problem, but if I try to define OutcomeParser that way, I get the error:
Type may not reference itself as a requirement
I wouldn't be so quick to dismiss type erasures as "hacky" or "working around [...] the type system" – in fact I'd argue that they work with the type system in order to provide a useful layer of abstraction when working with protocols (and as already mentioned, used in the standard library itself e.g AnySequence, AnyIndex & AnyCollection).
As you said yourself, all you want to do here is have the possibility of either returning a given result from a parser, or another parser that works with the same result type. We don't care about the specific implementation of that parser, we just want to know that it has a parse() method that returns a result of the same type, or another parser with that same requirement.
A type erasure is perfect for this kind of situation, as all you need to do is take a reference to a given parser's parse() method, allowing you to abstract away the rest of the implementation details of that parser. It's important to note that you aren't losing any type safety here, you're being exactly as precise about the type of the parser as you requirement specifies.
If we look at a potential implementation of a type-erased parser, AnyParser, hopefully you'll see what I mean:
struct AnyParser<Result> : Parser {
// A reference to the underlying parser's parse() method
private let _parse : () -> ParserOutcome<Result>
// Accept any base that conforms to Parser, and has the same Result type
// as the type erasure's generic parameter
init<T:Parser where T.Result == Result>(_ base:T) {
_parse = base.parse
}
// Forward calls to parse() to the underlying parser's method
func parse() -> ParserOutcome<Result> {
return _parse()
}
}
Now in your ParserOutcome, you can simply specify that the parser case has an associated value of type AnyParser<Result> – i.e any kind of parsing implementation that can work with the given Result generic parameter.
protocol Parser {
associatedtype Result
func parse() -> ParserOutcome<Result>
}
enum ParserOutcome<Result> {
case result(Result)
case parser(AnyParser<Result>)
}
...
struct BarParser : Parser {
func parse() -> ParserOutcome<String> {
return .result("bar")
}
}
struct FooParser : Parser {
func parse() -> ParserOutcome<Int> {
let nextParser = BarParser()
// error: Cannot convert value of type 'AnyParser<Result>'
// (aka 'AnyParser<String>') to expected argument type 'AnyParser<_>'
return .parser(AnyParser(nextParser))
}
}
let f = FooParser()
let outcome = f.parse()
switch outcome {
case .result(let result):
print(result)
case .parser(let parser):
let nextOutcome = parser.parse()
}
You can see from this example that Swift is still enforcing type-safety. We're trying to wrap a BarParser instance (that works with Strings) in an AnyParser type erased wrapper that expects an Int generic parameter, resulting in a compiler error. Once FooParser is parameterised to work with Strings instead of Int, the compiler error will be resolved.
In fact, as AnyParser in this case only acts as a wrapper for a single method, another potential solution (if you really detest type erasures) is to simply use this directly as your ParserOutcome's associated value.
protocol Parser {
associatedtype Result
func parse() -> ParserOutcome<Result>
}
enum ParserOutcome<Result> {
case result(Result)
case anotherParse(() -> ParserOutcome<Result>)
}
struct BarParser : Parser {
func parse() -> ParserOutcome<String> {
return .result("bar")
}
}
struct FooParser : Parser {
func parse() -> ParserOutcome<String> {
let nextParser = BarParser()
return .anotherParse(nextParser.parse)
}
}
...
let f = FooParser()
let outcome = f.parse()
switch outcome {
case .result(let result):
print(result)
case .anotherParse(let nextParse):
let nextOutcome = nextParse()
}
Status of the features needed to make this work:
Recursive protocol constraints (SE-0157) Implemented (Swift 4.1)
Arbitrary requirements in protocols (SE-0142) Implemented (Swift 4)
Generic Type Aliases (SE-0048) Implemented (Swift 3)
Looks like this is currently not possible without introducing boxed types (the "type erasure" technique), and is something looked at for a future version of Swift, as described by the Recursive protocol constraints and Arbitrary requirements in protocols sections of the Complete Generics Manifesto (since generic protocols are not going to be supported).
When Swift supports these two features, the following should become valid:
protocol Parser {
associatedtype Result
associatedtype SubParser: Parser where SubParser.Result == Result
func parse() -> ParserOutcome<Result, SubParser>
}
enum ParserOutcome<Result, SubParser: Parser where SubParser.Result == Result> {
case result(Result)
case parser(P)
}
With generic typealiases, the subparser type could also be extracted as:
typealias SubParser<Result> = Parser where SubParser.Result == Result
I think that you want to use a generic constraint on the ParserOutcome enum.
enum ParserOutcome<Result, P: Parser where P.Result == Result> {
case result(Result)
case parser(P)
}
This way you would not be able to use ParserOutcome with anything that is not conforming to the Parser protocol. You can actually add one more constraint to make it better. Adding the constraint that the result of the Parser outcome will be the same type as the Parser's associated type.
Note
Swift is changing rapidly, this question was asked regarding:
Xcode 7, Swift 2.0
Explanation
I'm looking to implement a generic return argument. Quite often, I find it necessary to implement an optional version overload so I can access the underlying type and handle it appropriately. Here's some manufactured functions. The assignment of String is just there as a placeholder for replication:
func ambiguous<T>() -> T {
let thing = "asdf"
return thing as! T
}
func ambiguous<T>() -> T? {
return nil
}
Now, if we look at the implementation:
// Fine
let a: String = ambiguous()
// Ambiguous
let b: String? = ambiguous()
This might seem obvious because you could assign type T to a variable of type T?. So it makes sense that it would have trouble inferring. The problem is, that with a type constraint, it suddenly works. (This can be anything, I'm using Equatable for easy replication.
func nonAmbiguous<T : Equatable>() -> T {
let thing: AnyObject = "asdf"
return thing as! T
}
func nonAmbiguous<T : Equatable>() -> T? {
return nil
}
And now, it functions as expected:
// Fine
let c: String = nonAmbiguous()
// Fine
let d: String? = nonAmbiguous()
Note, this also works with other type:
func nonAmbiguous<T>() -> [T] {
let thing: AnyObject = ["asdf"]
return thing as! [T]
}
func nonAmbiguous<T>() -> [T]? {
return nil
}
// Fine
let e: [String] = nonAmbiguous()
// Fine
let d: [String]? = nonAmbiguous()
Question:
Is there a way to have a return generic argument infer the appropriate overload through optionality?
if no
Is this a language feature, or a bug somewhere. If it's a language feature, please explain the underlying issue preventing the possibility of this behavior.
The first example is ambiguous because T can be inferred as both String
and String?.
The second example is not ambiguous because String is Equatable but String? is not, so T : Equatable cannot be inferred as String?.
The third case is not ambiguous because [T] is not
inferred as [String]?.
Remark: Generally, Optional<Wrapped> does not conform to Equatable
even if Wrapped does, in the same way as Array<Element>
does not conform to Equatable even if Element does.
This is a restriction of the current type system in Swift which
might be improved in a future version, compare
[swift-dev] RFC: Adding Optional variants of == for collections to the std lib.
from the Swift development mailing list.