Inferring the type of the return of a function from one of the inputs to that function - swift

In our app we have an enum defined that covers all the back end endpoints that can be hit by the app...
enum Route {
case todo(TodoRoute)
case event(EventRoute)
case userDetails
enum TodoRoute {
case create
case delete(Todo)
}
case EventRoute {
case list
case edit(Event)
}
}
These get translated into the individual endpoints and parameters and so on.
So we have a couple of functions on our ApiClient like this that eventually make the network call...
public func request<A: Decodable>(
_ route: Route,
as: A.Type
) async throws -> A {
let (data, _) = try await self.request(route)
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(A.self, from: data)
} catch {
throw error
}
}
public func request<A: Decodable>(
_ route: Route
) async throws -> A {
let (data, _) = try await self.request(route)
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(A.self, from: data)
} catch {
throw error
}
}
As you can see, these decode the returned data into a particular generic type.
So at the call site it looks like one of these...
// 1.
let result = try await apiClient.request(.userDetails, as: UserDetails.self)
// 2.
let result: EventList = try await apiClient.request(.event(.list))
These work but I'm trying to find a way to embed the type to be decoded into the function call itself.
Each endpoint we call will only return one type of JSON so there is a 1:1 mapping between our Route cases and the type returned by the function. So rather than having to explicitly define both the route AND the type in the function it should be possible to only provide the route and have the type inferred. Something like this...
let result = try await apiClient.request(.event(.list))
And have the type of result inferred from the Route passed into the function.
Perhaps this just isn't possible?
I was thinking of having a function on the route like route.resourceType or something? So the function can infer what T is from that? Or something?
Hmm... as I type I'm thinking that isn't possible?
Is it possible to make such and type inference work?

Just to make it clear this is answered.
As #RobNapier pointed out. Inferring a type based on a value is not something that is a feature within Swift. Although it does exist in other languages. It’s not one to wait for in Swift.
https://en.wikipedia.org/wiki/Dependent_type

Related

Is it possible for a Swift type to be inferred by "pulling out" a Type value from a generic function's parameter?

Introduction
(Apologies if the title is confusing, but I explain the question better here!)
I'm building a networking library that can perform JSON decoding on its responses.
Host apps adopting this library will create enums conforming to NetLibRoute. All that currently does is enforce the presence of asURL:
public protocol NetLibRoute {
var asURL: URL { get throws }
}
In a host app, I have a routing system that enforces API structure at the compiler-level (via enums and associated values) for each endpoint, like this:
enum Routes: NetLibRoute {
case people(Int?)
// Other routes go here, e.g.:
// case user(Int)
// case search(query: String, limit: Int?)
var asURL: URL {
let host = "https://swapi.dev/"
let urlString: String
switch self {
case let .people(personID):
if let personID {
urlString = host + "api/people/\(personID)"
} else {
urlString = host + "api/people/"
}
// Build other URLs from associated values
}
return URL(string: urlString)!
}
}
I also want each enum to be associated with a certain Codable type. I can do that, of course, by modifying the Route protocol declaration to also require a type conforming to Decodable:
protocol NetLibRoute {
var asURL: URL { get throws }
var decodedType: Decodable.Type { get } // This
}
And a matching computed property in my Routes enum:
var decodedType: Decodable.Type {
switch self {
case .people(_):
return Person.self
// And so on
}
}
The Problem
Currently, my networking code has a declaration something like this:
public static func get<T>(route: NetLibRoute,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
Which lets me call it like this:
let person = try await NetLib.get(route: Routes.people(1), type: Person.self)
However, this redundancy (and potential human error from mismatching route and type) really irks me. I really want to be able to only pass in a route, and have the resulting type be inferred from there.
Is there some way to get the compiler to somehow check the NetLibRoute enum and check its decodedType property, in order to know what type to use?
Ultimately, I want this networking function to take one parameter (a route) and infer the return type of that route (at compile-time, not with fragile runtime hacks or !s), and return an instance of the type.
Is this possible?
Potential Alternatives?
I'm also open to alternative solutions that may involve moving where the get function is called from.
For example, calling this get function on a route itself to return the type:
let person = try await Routes.people(1).get(type: Person.self) // Works, but not optimal
let person = try await Routes.people(1).get() // What I want
Or even on the type itself, by creating a new protocol in the library, and then extending Decodable to conform to it:
public protocol NetLibFetchable {
static var route: NetLibRoute { get }
}
extension Decodable where Self: NetLibFetchable {
public static func get<T>() async throws -> T where Self == T, T: Decodable {
// Call normal get function using inferred properties
return try await NetLib.get(route: route,
type: T.self)
}
Which indeed lets me call like this:
let person = try await Person.get() // I can't figure out a clean way to pass in properties that the API may want, at least not without once again passing in Routes.people(1), defeating the goal of having Person and Routes.people inherently linked.
While this eliminates the issue of type inference, the route can no longer be customized at call-time, and instead is stuck like this:
extension Person: NetLibFetchable {
public static var route: NetLibRoute {
Routes.people(1) // Can't customize to different ID Ints anymore!
}
}
Which makes this particular example a no-go, and leaves me at a loss.
Appreciation
Anyway, thank you so much for reading, for your time, and for your help.
I really want this library to be as clean as possible for host apps interacting with it, and your help will make that possible.
Are you wedded to the idea of using an enum? If not, you can do pretty much what you want by giving each enum value its own type and using an associated type to do what you want.
public protocol NetLibRoute
{
var asURL: URL { get throws }
associatedtype Decoded: Decodable
}
struct Person: Decodable
{
var name: String
}
struct Login: Decodable
{
var id: String
}
struct People: NetLibRoute
{
typealias Decoded = Person
var id: Int
var asURL: URL { return URL(filePath: "/") }
}
struct User: NetLibRoute
{
typealias Decoded = Login
var id: String
var asURL: URL { return URL(filePath: "/") }
}
func get<N: NetLibRoute>(item: N) throws -> N.Decoded
{
let data = try Data(contentsOf: item.asURL)
return try JSONDecoder().decode(N.Decoded.self, from: data)
}
let thing1 = try get(item: People(id: 1))
let thing2 = try get(item: User(id: "foo"))
Where you might have had a switch before to do different things with different Routes you would now use a function with overloaded arguments.
func doSomething(thing: Person)
{
// do something for a Person
}
func doSomething(thing: Login)
{
// do something else for a Login
}
doSomething(thing: thing1)
doSomething(thing: thing2)
I think the problem lays in this function.
public static func get<T>(route: Route,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
On the first hand, it uses concretions instead of abstractions. You shouldn't pass a Route here, it should use your protocol NetLibRoute instead.
On the other hand, I think that the type param is not needed. Afaik you can get the Type to Decode with the var:
NetLibRoute.decodedType
Am I missing something on this matter?
Apart from that, I'd rather go with struct instead of enum when trying to implement the Routes (concretions). Enums cannot be extended. So you won't be allowing the creation of new requests in client side, only in the library.
I hope I've helped.
PS: Some time ago I made this repo. Maybe that could help you (specially this class). I used Combine instead of async/await, but it's not relevant to what you need.

Initialize a placeholder empty variable of generic type

I'm new to Swift.
My ultimate goal is to return the value level (which contains decoded JSON) but I am struggling with returning data from my function.
This is my code:
func decode<T: Decodable>(_ type: T.Type, from file: String) -> T {
let decoder = JSONDecoder()
let urlString = "https://drive.google.com/u/0/uc?id=1WMjoHM2iaKjfx9wZhE_6jmKlcMi_OzGT&export=download"
var level = type;
self.loadJson(fromURLString: urlString) { (result) in
switch result {
case .success(let data):
level = try! decoder.decode(T.self, from: data)
case .failure(let error):
print(error)
}
}
return level
}
Current problem
The current problem is when I try to assign a value to level I get the following error Cannot assign value of type 'T' to type 'T.Type'
What I've tried
My assumption is the error is happening because initializing a variable with T.type is trying to initialize with the name of the Class instead of the Class itself. I tried to call type.self with no luck.
I also tried to get creative to not have to initialize an empty variable. I tried returning decoder.decode(T.self, from: data) but I was getting a weird Unexpected non-void return value in void function error. I have no idea what that is all about.
Is there an easy way to initialize an empty variable to return data later when given T.Type

Generic return type with Codable returning error Generic parameter 'T' could not be inferred even though Xcode correctly shows return type on output

I have looked at a variety of related questions on here, but none of the answers appear to work for me right now.
For starters, I have the following function set up with a generic return type that must conform to Decodable.
func readFile<T: Decodable>(url: URL) -> T? {
do {
let data = try Data(contentsOf: url)
return try PropertyListDecoder().decode(T.self,
from: data) as T
} catch {
return nil
}
}
I have tried a variety of calls against it, but thus far Xcode keeps returning the error code
Cannot explicitly specialize a generic function
After some experimentation, I have arrived at the following:
if let file: CodableStruct = readFile(url: url) as? CodableStruct {
// File is reported as the correct type here
// but I still get "Generic parameter 'T' could not be inferred"
}
My goal is to store a variety of different objects that conform to Codable, and then be able to retrieve them with the above function.
Just remove the conditional downcast as? CodableStruct
if let file: CodableStruct = readFile(url: url) {
and – not related to the error – remove also the redundant bridge cast as T.
Consider to make the function throw
func readFile<T: Decodable>(url: URL) throws -> T {
let data = try Data(contentsOf: url)
return try PropertyListDecoder().decode(T.self, from: data)
}
Forward the errors along. Don't hide them with an optional.
func readFile<Decodable: Swift.Decodable>(url: URL) throws -> Decodable {
try PropertyListDecoder().decode(
Decodable.self,
from: Data(contentsOf: url)
)
}
let file: CodableStruct = try readFile(url: url)
Turns out, the compiler was confused by the try command in my readFile function.
I had to add ( ) to help it understand the statement.
return (try PropertyListDecoder().decode(T.self,
from: data)) as T
However, vadian was correct in that I could remove the downcast from the call.
To the comments about throwing, I do handle the error, but I cut down the code for purpose of making the question shorter. Thank you for addressing that though!

How to make a function that returns a decodable type in Swift?

So I have this enum that I use for the few url requests I use in my app :
enum Netwrok {
case popular
case topRated
case latest
// ...
static let baseUrl = "http://..."
func path() -> String {
switch self {
case .popular: return "/popular"
// ...
}
}
}
And I would like to add a function that returns the Decodable Type of model the network stack should decode the data with.
So I thought something like that would do the job :
func returnType<T>() -> T.Type where T : Decodable {
switch self {
case .popular:
return Popular.self
// ...
}
}
But I can't make it work, it says :
Cannot convert return expression of type 'Popular.Type' to return type 'T.Type'
Asking me to force cast in T.Type.
How can I make a function that returns the decodable so that type can be handled but the JSONDecoder's decode function ?
Thanks.
What you're asking is straightforward, but it probably isn't what you want. What you're asking to do is to return a type. There's nothing generic about that.
func returnType<T>() -> T.Type where T : Decodable {
This syntax defines a type parameter, T, that is passed by the caller. It's not defined by your function. That means the caller may pass any type that is Decodable and your function will return it. For example, the caller can set T to be Int (since that's Decodable), and you will return Int.Type. That's easy to implement (return T.self), but not what you mean.
What you mean is that the function returns some type that is Decodable that the function knows, but the caller doesn't:
func returnType() -> Decodable.Type { ... }
This will work fine, and do exactly what you are asking for, but it suggests you're probably building this network stack incorrectly and will have headaches later.
The reason this approach is likely to be a problem is that you probably want to write a line of code like this:
let result = JSONDecoder().decode(networkType.returnType(), from: data)
That's going to break, because Decodable.Type is not itself a Decodable type. (You you decode Int, but you can't decode the type of Int.) Say it did work. What type would result be? What could you do with it? The only thing you'd know about it is that it's Decodable (and you've already decoded it).
You likely want something more like Vasu Chand's implementation, or the similar approach discussed in my blog series.
You can use escaping closure for your returning result of an API Call.
Assuming you are hitting a get request . A simple working example for passing Codable model for get request api.
class func GETRequest<ResponseType :Decodable>(url : URL,responseType : ResponseType.Type ,completion: #escaping (ResponseType? ,Error? ) -> Void){
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else{
completion(nil,error)
return
}
let decoder = JSONDecoder()
do{
let responseData = try decoder.decode(ResponseType.self, from: data)
completion(responseData, nil)
}
catch let error{
completion(nil, error)
}
}
task.resume()
}
How to call this network function.
Network.GETRequest(url: url, responseType: Model.self) { (model, error) in
completion(model,error)
}
Model class contains
struct Model : Codable{
}
You can pass any response model for any get request to network class .
Similarly you can build api network for post request where request body is simply Codable model .
For sorry you can't as according to your need the supply for the first parameter here
JSONDecoder().decode(AdecodableType.self,from:data)
need to be inferred right when you write the code so it can't be Any 1 from a collection of types that conform to Decodable

enums with Associated Values + generics + protocol with associatedtype

I'm trying to make my API Service as generic as possible:
API Service Class
class ApiService {
func send<T>(request: RestRequest) -> T {
return request.parse()
}
}
So that the compiler can infer the response type from the request categories .auth and .data:
let apiService = ApiService()
// String
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")))
// Int
let intResponse = apiService.send(request: .data(.content(id: "123")))
I tried to come up with a solution using generics and a protocol with associated type to handle the parsing in a clean way. However I'm having trouble associating the request cases with the different response types in a way that it's simple and type-safe:
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
Endpoints
enum RestRequest {
case auth(_ request: AuthRequest)
case data(_ request: DataRequest)
// COMPILER ERROR HERE: Generic parameter 'T' is not used in function signature
func parse<T: Parseable>() -> T.ResponseType {
switch self {
case .auth(let request): return (request as T).parse()
case .data(let request): return (request as T).parse()
}
}
enum AuthRequest: Parseable {
case login(email: String, password: String)
case signupWithFacebook(token: String)
typealias ResponseType = String
func parse() -> ResponseType {
return "String!!!"
}
}
enum DataRequest: Parseable {
case content(id: String?)
case package(id: String?)
typealias ResponseType = Int
func parse() -> ResponseType {
return 16
}
}
}
How is T not used in function signature even though I'm using T.ResponseType as function return?
Is there a better still clean way to achieve this?
I'm trying to make my API Service as generic as possible:
First, and most importantly, this should never be a goal. Instead, you should start with use cases, and make sure that your API Service meets them. "As generic as possible" doesn't mean anything, and only will get you into type nightmares as you add "generic features" to things, which is not the same thing as being generally useful to many use cases. What callers require this flexibility? Start with the callers, and the protocols will follow.
func send<T>(request: RestRequest) -> T
Next, this is a very bad signature. You don't want type inference on return types. It's a nightmare to manage. Instead, the standard way to do this in Swift is:
func send<ResultType>(request: RestRequest, returning: ResultType.type) -> ResultType
By passing the expected result type as a parameter, you get rid of the type inference headaches. The headache looks like this:
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")))
How is the compiler to know that stringResponse is supposed to be a String? Nothing here says "String." So instead you have to do this:
let stringResponse: String = ...
And that's very ugly Swift. Instead you probably want (but not really):
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")),
returning: String.self)
"But not really" because there's no way to implement this well. How can send know how to translate "whatever response I get" into "an unknown type that happens to be called String?" What would that do?
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
This PAT (protocol w/ associated type) doesn't really make sense. It says something is parseable if an instance of it can return a ResponseType. But that would be a parser not "something that can be parsed."
For something that can be parsed, you want an init that can take some input and create itself. The best for that is Codable usually, but you could make your own, such as:
protocol Parseable {
init(parsing data: Data) throws
}
But I'd lean towards Codable, or just passing the parsing function (see below).
enum RestRequest {}
This is probably a bad use of enum, especially if what you're looking for is general usability. Every new RestRequest will require updating parse, which is the wrong place for this kind of code. Enums make it easy to add new "things that all instances implement" but hard to add "new kinds of instances." Structs (+ protocols) are the opposite. They make it easy to add new kinds of the protocol, but hard to add new protocol requirements. Requests, especially in a generic system, are the latter kind. You want to add new requests all the time. Enums make that hard.
Is there a better still clean way to achieve this?
It depends on what "this" is. What does your calling code look like? Where does your current system create code duplication that you want to eliminate? What are your use cases? There is no such thing as "as generic as possible." There are just systems that can adapt to use cases along axes they were prepared to handle. Different configuration axes lead to different kinds of polymorphism, and have different trade-offs.
What do you want your calling code to look like?
Just to provide an example of what this might look like, though, it'd be something like this.
final class ApiService {
let urlSession: URLSession
init(urlSession: URLSession = .shared) {
self.urlSession = urlSession
}
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: #escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = try? JSONDecoder().decode(Response.self, from: data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
}
This is very generic, and can apply to numerous kinds of use cases (though this particular form is very primitive). You may find it doesn't apply to all your use cases, so you'd begin to expand on it. For example, maybe you don't like using Decodable here. You want a more generic parser. That's fine, make the parser configurable:
func send<Response>(request: URLRequest,
returning: Response.Type,
parsedBy: #escaping (Data) -> Response?,
completion: #escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = parsedBy(data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
Maybe you want both approaches. That's fine, build one on top of the other:
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: #escaping (Response?) -> Void) {
send(request: request,
returning: returning,
parsedBy: { try? JSONDecoder().decode(Response.self, from: $0) },
completion: completion)
}
If you're looking for even more on this topic, you may be interested in "Beyond Crusty" which includes a worked-out example of tying together parsers of the kind you're discussing. It's a bit dated, and Swift protocols are more powerful now, but the basic message is unchanged and the foundation of things like parsedBy in this example.