Result type with generic Success Type - swift

I'm switching over a project from a custom Result type to the native Swift Result type and keep running into this - or similar - error messages:
Member 'success' in 'Result<_, Error>' produces result of type 'Result<Success, Failure>', but context expects 'Result<_, Error>'
protocol DataFetcher {
func request<T>(completion: #escaping (Result<T, Error>) -> Void )
}
struct RandomFetcher: DataFetcher {
func request<String>(completion: #escaping (Result<String, Error>) -> Void) {
let str = "Hello"
completion(.success(str))
}
}
The idea is to have make a bunch of generic Fetchers for different data extraction calls and to pass these to VC's who would have a var fetcher: DataFetcher property. The VC's know which concrete types they expect from their request. I can't use an associated type as I need to save a bunch of these in an array and I thought I could get away with just the generic implementation - but it almost seems as if the Result type being declared as a generic in the protocol, means that it won't accept when I specify it in the implementation. What am I missing?

func request<String>(completion: #escaping (Result<String, Error>) -> Void) {
This is a classic generics mistake in Swift. This does not mean "request requires T==String." This means "there is an arbitrary type called String that this function accepts." There is no relationship between this String and Swift.String.
What you're trying to do here violates the protocol. The protocol says that the caller gets to pick any T they want. You can't conform to that protocol with a restricted T.
If you want the conforming type (RandomFetcher) to get to decide the type of T, then you have to use an associatedType (that's what associatedType means).
The thing you're trying to build is pretty common, but not trivial, and requires a different way of thinking about the problem. I walk through it step-by-step in this series.

In this case an associated type is preferable
protocol DataFetcher {
associatedtype FetchType
func request(completion: #escaping (Result<FetchType, Error>) -> Void )
}
struct RandomFetcher: DataFetcher {
func request(completion: #escaping (Result<String, Error>) -> Void) {
let str = "Hello"
completion(.success(str))
}
}

Related

swift, troubles with generic parameters and constraints

I'm writing some tests and mocks for my product but I'm having troubles with generic parameters and constraints...
enum ApiResult<Success, Failure> where Failure: Error {
case success(Success)
case failure(Failure)
case disconnected
}
var askedDecodableApiResult: ApiResult<Decodable, Error> = .disconnected
func complete<T>(callback: #escaping (ApiResult<T, Error>) -> Void) where T: Decodable {
callback(askedDecodableApiResult) // 💥 Cannot convert value of type 'ApiResult<Decodable, Error>' to expected argument type 'ApiResult<T, Error>'
}
I'm lost with that error. What should I do to be able to send in my callback the pre-defined answer ? In the end, I want to be able to give my askedDecodableApiResult any ApiResult sporting a value that either inherits from Decodable or inherits from Error.
I need your help, thanks in advance :)

Using Generic parameter in closure

I have a function that makes a service call to fetch data, uses JSONDecoder to decode the JSON response into a model object and then return that object as part of a parameter in the completion block provided to the function. In the object, I am actually using the Result object.
Here is the code -
static func fetchData(_ completion: #escaping (Result<ExampleModelObject, Error>) -> Void)
I am trying to make this function generic, where it can accept the url it needs to call as one parameter and the Model object as another parameter and so I modified it to this -
static func fetchData <T: Decodable>(urlString: String, _ completion: #escaping (Result<T, Error>) -> Void)
This is the code where I use jsondecoder -
let parsedJSON = try jsonDecoder.decode(T.self, from: data)
completion(.success(parsedJSON))
Here is how I am trying to call this function from another class -
DataRetriever. fetchData(urlString: dataURLString) { (result: ExampleModelObject) in
However, I am getting 2 errors during compilation -
Cannot convert value of type '(ExampleModelObject) -> Void' to expected argument type '(Result<_, Error>) -> Void'
Generic parameter 'T' could not be inferred
Would anyone please be able to help me with how I can fix these errors?
You have to specify the full type
DataRetriever.fetchData(urlString: dataURLString) { (result: Result<ExampleModelObject,Error>) in
try to
fetchData(urlString: "") { (model: Result<ExampleModelObject, Error>) in
}
or use two completion blocks like so:
func fetchData <T> (urlString: String, success: #escaping(T) -> Void, failure: #escaping(Error) -> Void) where T: Decodable {
}
execution:
fetchData(urlString: "") { (model: ExampleModelObject) in
} failure: { error in
}
With an additional parameter you can help the combiler to recognise the type.
If you define the function in this way, it is no longer necessary to specify the type in the closure parameter.
static func fetchData <T: Decodable>(type: T.Type, urlString: String, _ completion: #escaping (Result<T, Error>) -> Void)
The call then looks like this:
DataRetriever.fetchData(type: ExampleModelObject.self, urlString: dataURLString) { result in }

Swift protocol conformance when returning a generic

Here's an example:
protocol Feed {
func items<T>() -> [T]? where T: FeedItem
}
protocol FeedItem {}
class FeedModel: Feed, Decodable {
func items<T>() -> [T]? where T : FeedItem {
return [FeedItemModel]() // Error: Cannot convert return expression of type '[FeedItemModel]' to return type '[T]?'
}
}
class FeedItemModel: FeedItem, Decodable {}
Why does it:
A) try to convert to T when T is a generic, not a type?
B) does not recognize FeedItemModel as conforming to FeedItem?
func items<T>() -> [T]? where T : FeedItem
This says that the caller can define T to be whatever they want, as long as T conforms to FeedItemModel, and this function will return an optional array of those.
FeedItemModel is something that conforms to FeedItem, but it is not promised to be the type T that the caller requested.
As an example, consider:
class OtherModel: FeedItem {}
According to your function signature, I can do this:
let ms: [OtherModel]? = FeedModel().items()
But your function won't then return [OtherModel]? to me. I suspect you don't actually mean this to be generic. I expect you mean:
func items() -> [FeedItemModel]?
or possibly
func items() -> [FeedItem]?
(Though I would think very hard before doing the latter one and make sure that the protocol existential is really doing useful work here.)
A)
T is a type, a homogenous concrete type specified at runtime.
Imaging T is class Foo : FeedItem it's obvious that FeedItemModel cannot be converted to Foo
B)
FeedItemModel is recognized as conforming to FeedItem but this is irrelevant.
It's often a mix-up of generics and protocols. Generic types are not covariant. If you need covariant types use an associated type.
Either you can ignore generics because because it only applies to that one function and it isn't needed since directly saying that the return type is [FeedItem]? yields the same result
protocol Feed {
func items() -> [FeedItem]?
}
class FeedModel: Feed, Decodable {
func items() -> [FeedItem]? {
return [OtherModel]()
}
}
If you on the other hand want a generic protocol then you should use a associated type
protocol Feed2 {
associatedtype T: FeedItem
func items() -> [T]?
}
class FeedModel2: Feed2, Decodable {
typealias T = FeedItemModel
func items() -> [T]? {
return [FeedItemModel]()
}
}

Generic completion passed as non-generic

I'm working on some framework and faced a problem.
I have a public protocol:
public protocol MyPublicProtocol1 {
}
And another one, wich contains a function with generic argument passed. Generic argument has a constraint – argument type must implement the first public protocol:
public protocol MyPublicProtocol2 {
func someFunc<T: MyPublicProtocol1>(completion: (T) -> ())
}
Then I'm implementing my protocols not in public classes. Inside that function with generic argument I have to call another one that takes not generic argument and look like that:
func anotherFuncWith(completion: (MyPublicProtocol1) -> ())
And here's what implementation looks like:
class MyPublicProtocol1Impl: MyPublicProtocol1 {
}
class MyPublicProtocol2Impl: MyPublicProtocol2 {
func someFunc<T: MyPublicProtocol1>(completion: (T) -> ()) {
anotherFuncWith(completion: completion)
}
}
And of course I have an error in the last string.
I can't declare someFunc(completion:) with not a generic argument like:
func someFunc(completion: (MyPublicProtocol1Impl) -> ())
Because MyPublicProtocol1Impl class mustn't be public. And I also can't declare anotherFuncWith(completion:) to take generic argument too for some reasons.
Is there a way to somewhat "convert" (T: MyPublicProtocol1) -> () completion to be just a (MyPublicProtocol1) -> ()?
Any help or advices are very appreciated! And thank you for reading my story!
You've asked for something that is not provably true. You have a method:
func anotherFuncWith(completion: (MyPublicProtocol1) -> ())
This accepts a method that can receive any MyPublicProtocol1. You then pass it a method of type:
(T: MyPublicProtocol1) -> ()
anotherFuncWith may pass something that is not T, at which point this is undefined. To make it more concrete, let's get rid of most of the stuff here and make MyPublicProtocol1 be Any (just to pick a trivial protocol).
func anotherFuncWith(completion: (Any) -> ()) {
completion("This is a string which is an Any, so that's fine")
}
func someFunc<T: Any>(completion: (T) -> ()) {
anotherFuncWith(completion: completion)
}
This fails to compile exactly like your example. Now let's think through what I could do if it did compile. I could call:
func complete(x: Int) -> () {
print(x + 1)
}
someFunc(completion: complete)
So now anotherFuncWith calls complete passing a String, which can't be added. Crash.
The underlying problem here is that you've gotten covariance and contravariance backwards.
How do we fix it? Depends on what you really mean. This code is a little strange. Do you care about the actual type of T or not? You never seem to use it. If you don't care, then just use protocols:
public protocol MyPublicProtocol2 {
func someFunc(completion: (MyPublicProtocol1) -> ())
}
If you do care about the actual type, use a PAT:
public protocol MyPublicProtocol2 {
associatedtype T: MyPublicProtocol1
func someFunc(completion: (T) -> ())
}
Or you may want to rethink whether you need a protocol here at all. I often find people reach for protocols when they don't need them yet. Do you have multiple implementations of these protocols? Do you have multiple types that are passed? If not, I'd simplify and only go generic/protocol when you have a real problem you're solving in the current code. (You may need them; this is just my stock advice that many people have found useful when they've over-designed.)
The dirty way to get around this is
func someFunc<T: MyPublicProtocol1>(completion: (T) -> ()) {
anotherFuncWith { (thing) in
if let tThing = thing as? T {
completion(tThing)
}
}
}
I would only do this if you are very sure of the code surrounding it as it is certainly fallible.
Also, this works too. I'm unsure what you're actually trying to do so I'm not sure if it solves your problem
func anotherFuncWith<T: MyPublicProtocol1>(completion: (T) -> ()) {
}
class MyPublicProtocol2Impl: MyPublicProtocol2 {
func someFunc<T: MyPublicProtocol1>(completion: (T) -> ()) {
anotherFuncWith(completion: completion)
}
}

Generic parameter T could not be inferred. Factory methods

Can someone explain to me why this wouldn't work?
I have a class with factory methods like this:
public class NetworkTask<T> {
var request: URLRequest
var completionHandler: NetworkResponse<T> -> Void
init(request: URLRequest, completionHandler: NetworkResponse<T> -> Void) {
self.request = request
self.completionHandler = completionHandler
}
static func dataResponseTaskWithRequest(request: URLRequest, completionHandler: NetworkResponse<NSData> -> Void) -> NetworkTask<NSData> {
return NetworkTask<NSData>(request: request, completionHandler: completionHandler)
}
static func mappedObjectResponseTaskWithRequest<MappedType>(request: URLRequest, completionHandler: NetworkResponse<MappedType> -> Void) -> NetworkTask<MappedType> {
return NetworkTask<MappedType>(request: request, completionHandler: completionHandler)
}
}
Then, after happily knowing that it compiles, I go to create a Task like this:
let task = NetworkTask.dataResponseTaskWithRequest(URLRequest()) { (response) in
}
Nope...
Generic parameter T could not be inferred
Wait, I can clearly infer it, the method returns NetworkTask<NSData>, so T is NSData.
Ok... then, maybe like this?
let task: NetworkTask<NSData> = NetworkTask.dataResponseTaskWithRequest(URLRequest()) { (response) in
}
Nope...
Cannot invoke 'dataResponseTaskWithRequest' with an argument list of type '(URLRequest, (_) -> _)'
Ok, so maybe the other method:
let task = NetworkTask.mappedObjectResponseTaskWithRequest(URLRequest()) { (response: NetworkResponse<String>) in
}
Nope...
Cannot convert value of type '(NetworkResponse) -> ()' to expected
argument type 'NetworkResponse<_> -> Void'
I must be clearly missing something here because the compiler can't have so many errors. Does anybody have any clue?
NetworkTask<T> is the type, not NetworkTask. That is, the parameter T is on the class and everything you do with that class, also to access its class methods, require describing that type.
Even though the T is not included in the method declaration that gives you the compiler error, there is no class NetworkTask that would contain all the class methods where the type parameter is not included – imagine instead that the method is on all classes NetworkTask<T> for any value of T. This is similar to C++ where the corresponding is even called "template", meaning that your class declaration with generic type parameters is used as a template for literally compiling different classes. This is different to for instance Java where the generics syntax is just compile time sugar with type erasure (you could indeed there call the class method – only one class really exists in that case).
Here's a minimal example to demo this some more:
class A<T> {
class func foo() {
}
class func bar(t:T) -> Void {
}
}
class B {}
A.foo() // this gives an error because the type cannot be inferred.
A.bar(1) // this works fine without compiler errors as the integer literal type can be inferred there.
In the example case above, A would be fine to called for instance with:
A<IntegerLiteralType>.foo()
You should perhaps consider whether the methods in this case belong in the class that has that type parameter T, or whether they should have something else as a receiver (or indeed whether they should be free functions?).