How to differentiate what generic value is what in Swift? - swift

How can I differentiate what generic value is for what in Swift?
For example, what does the value 'T' do and what does the value 'E' do?
func ??<T, E>(result: Result<T, E>, handleError: (E) -> T) -> T {
switch result {
case let .success(value):
return value
case let .failure(error):
return handleError(error)
}
}

what does the value 'T' do and what does the value 'E' do?
They're not "values", they're the names of types — on a par with a term like String or Int. In fact, T could be String or Int. But E has to be some type of Error.
So the phrase <T, E>, which appears twice, just refers to the fact that T and E are generic placeholders for their real types. When someone actually calls this ?? function, the caller will make clear what T and E really are. This is called resolving the generic.
So let's imagine that we call ?? in such a way as to resolve T to String and E to Error. Then in the mind of the compiler, we'll have this:
func ??(result: Result<String, Error>, handleError: (Error) -> String) -> String {
switch result {
case let .success(value):
return value
case let .failure(error):
return handleError(error)
}
}
So now we can read the function declaration. It says: "You hand me two parameters. One, result:, must be a Result enum whose success type is String and whose failure type is Error. The other, handleError:, must be a function that takes an Error and returns a String. And I will return a String to you."
Except, of course, that that is only one way out of an infinite number of ways to resolve T and E. They stand in for the real types that will be resolved a compiled time, depending on how ?? is actually called. So there's your answer; that is "what they do". They are placeholders standing for the real types that will be resolved at compile time.
And in fact, to demonstrate, I will call your function in a way that resolves T to String and E to Error (though I will rename your function myFunc to make the name legal):
func myFunc<T, E>(result: Result<T, E>, handleError: (E) -> T) -> T {
switch result {
case let .success(value):
return value
case let .failure(error):
return handleError(error)
}
}
enum MyError : Error { case oops }
let r = Result<String, Error> { throw MyError.oops }
let output = myFunc(result:r) { err in "Ooops" }
print(output) // Ooops
Footnote: Note that your function cannot really be called ??, as that name is taken. It might have been better to call it foo (the usual nonsense name in these situations).

#matt's answer nails it, so I thought I'd add a like bit of higher level commentary.
Type parameter naming
The use of single characters for these generic type parameters is bit of a legacy a convention from Java and C#, but it need not be so concise. In this example, if you look at the main constraint on the types (Result), note that they use Success and Failure. Using these here would provide a clearer idea about the intent of this function:
func ??<Success, Failure>(result: Result<Success, Failure>, handleError: (Failure) -> Success) -> Success
Thus, a function that takes:
a Result that can either contain a Success or a Failure, and
a closure that takes a Failure and returns a Success
and returns:
a Success
Implementations bourne out of type system constraints
Note that as none of these types are wrapped in Optional the implementation of this function is almost entirely constrained (side-effects notwithstanding).
Take, for example, the simplest function that could seemingly match:
func ?? <Success, Failure>(result: Result<Success, Failure>, handleError: (Failure) -> Success) -> Success {
return Success()
}
Attempting to compile this gives the following error:
main.swift:2:12: error: type 'Success' has no member 'init'
return Success()
^~~~~~~
due to the fact that the Success type is entirely unconstrained in Result, so we don't actually know how to create one inside the function.
We know that Result can contain a Success, so what if we try and forcibly get that?
func ?? <Success, Failure>(result: Result<Success, Failure>, handleError: (Failure) -> Success) -> Success {
return result.get()
}
This now fails with the following compiler error:
main.swift:2:12: error: call can throw, but it is not marked with 'try' and the error is not handled
return result.get()
^
due to the fact that this function has explicitly denoted that it will not throw, and Result.get() will throw the contained Failure if it does not contain a Success.
The other way to get the Success out of a Result is pattern matching, let's see how that works out matching the enumeration case pattern for a single if:
func ?? <Success, Failure>(result: Result<Success, Failure>, handleError: (Failure) -> Success) -> Success {
if case let .success(success) = result {
return success
}
}
When attempting this, the compilation error is (quite sensibly):
main.swift:5:1: error: missing return in a function expected to return 'Success'
}
^
So we need to also handle the case when a Result contains a Failure. Let's try with another pattern match, using the provided handleError closure:
func ?? <Success, Failure>(result: Result<Success, Failure>, handleError: (Failure) -> Success) -> Success {
if case let .success(success) = result {
return success
}
if case let .failure(failure) = result {
return handleError(failure)
}
}
This still gives the same error as the previous attempt, as the logic could still fall through in this case (this seems unlikely, but this could be subject to a kind of time-of-check to time-of-use bug).
Let's attempt it again, but matching both patterns in a switch:
func ?? <Success, Failure>(result: Result<Success, Failure>, handleError: (Failure) -> Success) -> Success {
switch result {
case let .success(success):
return success
case let .failure(failure):
return handleError(failure)
}
}
There we go, compiling without error.
As is obvious, this is the original function, as provided in your question.
Now that we've arrived back here, can we trim down this implementation? We could try something like:
func ?? <Success, Failure>(result: Result<Success, Failure>, handleError: (Failure) -> Success) -> Success {
switch result {
case let .success(success):
return success
}
}
but this provides this compilation error:
main.swift:2:5: error: switch must be exhaustive
switch result {
^
main.swift:2:5: note: add missing case: '.failure(_)'
switch result {
^
which indicates that we need that .failure(_) case to match all possible outcomes of the result.

Related

extension for Optional Task

Can we make an extension of Optional type where wrapped type is async Task which is generic struct itself? I al so need to use Task underlying types inside.
Example of what I need (which is not working itself of course).
Need such parametrised extension, not just generic method inside clean Optional extension. Bottom example produces error "Cannot find type 'E' in scope".
extension Optional where Wrapped == Task<T, E> {
}
UPD:
I've tried to use the next solution but then I can't get the underlying type of wrapped Task to construct methods. It produces error: 'Success' is not a member type of type 'Wrapped'.
extension Optional where Wrapped == Task<Sendable, Error> {
func value(or operation: #escaping #Sendable () async -> Wrapped.Success) async throws {
let task: Task<Wrapped.Success, Wrapped.Failure>
}
}
UPD: More concrete example
extension Optional where Wrapped == Task<Sendable, Error> {
func value<Success: Sendable>(or operation: #escaping #Sendable () async throws -> Success) async throws -> Success {
let task: Task<Success, Error>
switch self {
case let .some(runningTask): task = runningTask //Cannot assign value of type 'Task<any Sendable, any Error>' to type 'Task<Success, any Error>'
case .none:
task = Task {
return try await operation()
}
}
return try await task.value
}
}
Extension where clauses cannot introduce new type parameters. This is just a current limitation of Swift syntax, and may be improved in the future. But it is always fixable by attaching the where clause to each method, which can introduce new type variables.
extension Optional {
func value<Success: Sendable>(or operation: #escaping #Sendable () async throws -> Success) async throws -> Success
where Wrapped == Task<Success, Error> // <===
{
...
}
}
If there are many methods with the same where clause this is slightly tedious, but it is always equivalent to putting it on the extension.

How can I implement a custom error throwing syntax in swift?

I want to implement the following:
throwingFunction()??.doStuff()
/* if throwingFunction throws an error:
print the error
else
returns an object with the doStuff() Method
*/
throwingFunction()??
/*
if an error is thrown,
prints the error.
else
execute the function without errors.
*/
I'm not sure where to look in the source code for examples on how do, try, catch were implemented. The Swift error docs explain how to use error handle methods that are already implemented. To be clear, I want to implement custom error handling with the above syntax.
Something like:
precedencegroup Chaining {
associativity: left
}
infix operator ?? : Chaining
extension Result {
// ERROR: Unary operator implementation must have a 'prefix' or 'postfix' modifier
static func ??(value: Result<Success, Failure>) -> Success? {
switch value {
case .success(let win):
return win
case .failure(let fail):
print(fail.localizedDescription)
return nil
}
}
}
You can define a postfix operator which takes a throwing closure as (left) operand. ?? is already defined as an infix operator, therefore you have to choose a different name:
postfix operator <?>
postfix func <?><T>(expression: () throws -> T) -> T? {
do {
return try expression()
} catch {
print(error)
return nil
}
}
Now you can call
let result = throwingFunc<?>
or chain it with
let result = (throwingFunc<?>)?.doStuff()
Previous answer:
?? is already defined as an infix operator. For a postfix operator you have to choose a different name, for example:
postfix operator <?>
extension Result {
static postfix func <?>(value: Result) -> Success? {
switch value {
case .success(let win):
return win
case .failure(let fail):
print(fail.localizedDescription)
return nil
}
}
}
Now you can call
let res = Result(catching: throwingFunc)<?>
or chain it with
let res = (Result(catching: throwingFunc)<?>)?.doStuff()
There is little chance that you can do this, without actually forking apple/swift and creating your own version of the compiler... Here are my attempts:
First, I noticed that the second part of the desired result, ?.doStuff() looks exactly like a optional chaining expression. I thought I could make a postfix ? operator that returned an optional. But it turns out, I can't declare an ? operator at all:
postfix operator ? // error
So I used a visually similar character - ‽ - instead. The type of a throwing function is () throws -> Void and I used #autoclosure so that the {} can be omitted:
typealias ThrowingFunction<T> = () throws -> T
postfix operator ‽
postfix func ‽<T>(lhs: #autoclosure ThrowingFunction<T>) -> T? {
switch Result(catching: lhs) {
case .failure(let error):
print(error.localizedDescription)
return nil
case .success(let t):
return t
}
}
// Usage:
func f() throws -> Int {
throw URLError(URLError.badURL)
}
// You have to use it like this :(
(try f()‽)
(try f()‽)?.description
The try could be omitted if the function you are calling takes no arguments:
f‽
(f‽)?.description
To make functions of other arity work without try, you need to create an implementation of ‽ for each arity, which sucks.
But the brackets must be there because of how Swift parses operators :(
Then I tried to make the approach you attempted, with key paths:
func ??<T, U>(lhs: #autoclosure ThrowingFunction<T>, rhs: KeyPath<T, U>) -> U? {
switch Result(catching: lhs) {
case .failure(let error):
print(error.localizedDescription)
return nil
case .success(let t):
return t[keyPath: rhs]
}
}
func f() throws -> Int {
throw URLError(URLError.badServerResponse)
}
This seems to be even worse, because you gotta use it like this:
try f() ?? \.description
You can't omit the try,
f ?? \.description // type inferencer freaks out, thinks T is ThrowingFunction<Int>
nor reduce the spaces on either side of ?? (See here for why):
try f()??\.description
Plus there is this backlash that is an integral part of the keypath syntax, and you can only use it for key paths, not methods. :(
Summary
You can't do this because:
You can't overload ?
You can't put a ? right after anything because it will be parsed as optional chaining
You must write try, unless you cater for every arity.
postfix operator *
#discardableResult
postfix func *<Preferred>(expression: ErrorAlt<Preferred>) -> Preferred? {
switch expression {
case .preferred(let pref):
return pref
case .error(let err):
print(err.localizedDescription)
return nil
case .initializersWereNil:
print("initializersWereNil")
return nil
}
}
Here is an example usage.
enum TestMeError: Error {
case first
}
extension Int {
func printWin() {
print("we did it!")
}
}
func testMe() -> ErrorAlt<Int> {
if true {
return .error(TestMeError.first)
} else {
return .preferred(40)
}
}
// USAGE
testMe()*

Swift `Failure` keyword meaning in a Swift Combine chain error

Let's say that I've this simple chain that from an HTTP request creates a publisher for <T, APIManagerError>
func run<T:Decodable>(request:URLRequest)->AnyPublisher<T, APIManagerError>{
return URLSession.shared.dataTaskPublisher(for: request)
.map{$0.data}
.decode(type: T.self, decoder: JSONDecoder())
.eraseToAnyPublisher()// it should run mapError before this point
}
This code produces this error since I'm returning Error instead of APIManagerError.
Cannot convert return expression of type
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'
(aka 'AnyPublisher<T, Error>')
to return type 'AnyPublisher<T, RestManagerError>'
I know that to fix the issue I need to add a mapError after .decode.
.mapError{error in
APIManagerError.error("Decode Fail")
}
but I can't really understand what is reported with the error message before the "aka" part that is quite clear instead
How do you read the error Publishers.Decode<Upstream, Output, Coder>.Failure? specifically what does it mean the .Failure part? where can I find the Failure in the Swift Doc?
Since we are talking about two different kinds of errors here lets denote a Compilation Error as CE and the Failure of a Publisher (conforming to Swift.Error) as PF (Publisher Failure).
Your question is about the interpretation of the CE message.
Cannot convert return expression of type
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'
Writes out the resulting returned type of your implementation of func run - without the mapError call. The compiler acknowledges your call to eraseToAnyPublisher() in the end of the function, and also your generic Output of type T. So that covers the Cannot convert return expression of type 'AnyPublisher<T,. As to Publishers.Decode<Upstream, Output, Coder>.Failure>' outputs the derived type of the Failure. This is somewhat a symbolic breakdown of the derived Failure type. Your upstream Publisher is initially of type URLSession.DataTaskPublisher, as a result of your URLSession.shared.dataTaskPublisher call, which you then transform with every Combine operator you call: map and then decode. Resulting in the publisher Publishers.Decode. And the Failure type of cannot be properly "desymbolised" (I lack the proper compiler knowledge to use the correct terminology).
Which Xcode version do you use? The New Diagnostic Architecture might be able to show a better error message. This is in fact the reason why I later in my response use .assertMapError(is: DecodingError.self)
Your code in mapError does the job, but it completely tossed away information about the actual error. So I would not do that. At least print (log) the error. But still I would do something like:
Declare custom Error type
Intuitively we have at least two different kinds of errors, either networking or decoding. But potentially more...
public enum HTTPError: Swift.Error {
indirect case networkingError(NetworkingError)
indirect case decodingError(DecodingError)
}
public extension HTTPError {
enum NetworkingError: Swift.Error {
case urlError(URLError)
case invalidServerResponse(URLResponse)
case invalidServerStatusCode(Int)
}
}
You might need to tell combine that the error type is indeed DecodingError, thus I've declared some fatalError macros useful for this information. It is somewhat simular to Combine's setFailureType (but which only works when the upstream publisher has Failure type Never, thus we cannot use it here).
castOrKill
func typeErasureExpected<T>(
instance incorrectTypeOfThisInstance: Any,
toBe expectedType: T.Type,
_ file: String = #file,
_ line: Int = #line
) -> Never {
let incorrectTypeString = String(describing: Mirror(reflecting: incorrectTypeOfThisInstance).subjectType)
fatalError(
"Incorrect implementation: Expected variable '\(incorrectTypeOfThisInstance)' (type: '\(incorrectTypeString)') to be of type `\(expectedType)`",
file, line
)
}
func castOrKill<T>(
instance anyInstance: Any,
toType: T.Type,
_ file: String = #file,
_ line: Int = #line
) -> T {
guard let instance = anyInstance as? T else {
typeErasureExpected(instance: anyInstance, toBe: T.self, file, line)
}
return instance
}
And then create a convenience method on Publisher, similar to setFailureType:
extension Publisher {
func assertMapError<NewFailure>(is newFailureType: NewFailure.Type) -> AnyPublisher<Output, NewFailure> where NewFailure: Swift.Error {
return self.mapError { castOrKill(instance: $0, toType: NewFailure.self) }.eraseToAnyPublisher()
}
}
Usage:
I took the liberty of catching some more errors in your examples. Asserting e.g. that the server responds with a non failure HTTP status code etc.
func run<Model>(request: URLRequest) -> AnyPublisher<Model, HTTPError> where Model: Decodable {
URLSession.shared
.dataTaskPublisher(for: request)
.mapError { HTTPError.NetworkingError.urlError($0) }
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse else {
throw HTTPError.NetworkingError.invalidServerResponse(response)
}
guard case 200...299 = httpResponse.statusCode else {
throw HTTPError.NetworkingError.invalidServerStatusCode(httpResponse.statusCode)
}
return data
}
.decode(type: Model.self, decoder: JSONDecoder())
// It's unfortunate that Combine does not pick up that failure type is `DecodingError`
// thus we have to manually tell the Publisher this.
.assertMapError(is: DecodingError.self)
.mapError { HTTPError.decodingError($0) }
.eraseToAnyPublisher()
}
Bonus - equality check for HTTPError
It is indeed very advantageous if our error types are Equatable, it makes writing unit tests so much easier. Either we go the Equatable route, or we can do some reflection magic. I will present both solutions, but the Equatable solution is more robust for sure.
Equatable
In order to make HTTPError conform to Equatable we only need to manually make DecodingError equatable. I've done this with this code:
extension DecodingError: Equatable {
public static func == (lhs: DecodingError, rhs: DecodingError) -> Bool {
switch (lhs, rhs) {
/// `typeMismatch` is an indication that a value of the given type could not
/// be decoded because it did not match the type of what was found in the
/// encoded payload. As associated values, this case contains the attempted
/// type and context for debugging.
case (
.typeMismatch(let lhsType, let lhsContext),
.typeMismatch(let rhsType, let rhsContext)):
return lhsType == rhsType && lhsContext == rhsContext
/// `valueNotFound` is an indication that a non-optional value of the given
/// type was expected, but a null value was found. As associated values,
/// this case contains the attempted type and context for debugging.
case (
.valueNotFound(let lhsType, let lhsContext),
.valueNotFound(let rhsType, let rhsContext)):
return lhsType == rhsType && lhsContext == rhsContext
/// `keyNotFound` is an indication that a keyed decoding container was asked
/// for an entry for the given key, but did not contain one. As associated values,
/// this case contains the attempted key and context for debugging.
case (
.keyNotFound(let lhsKey, let lhsContext),
.keyNotFound(let rhsKey, let rhsContext)):
return lhsKey.stringValue == rhsKey.stringValue && lhsContext == rhsContext
/// `dataCorrupted` is an indication that the data is corrupted or otherwise
/// invalid. As an associated value, this case contains the context for debugging.
case (
.dataCorrupted(let lhsContext),
.dataCorrupted(let rhsContext)):
return lhsContext == rhsContext
default: return false
}
}
}
extension DecodingError.Context: Equatable {
public static func == (lhs: DecodingError.Context, rhs: DecodingError.Context) -> Bool {
return lhs.debugDescription == rhs.debugDescription
}
}
Which, as you can see, also has to make DecodingError.Context Equatable.
Then you can declare these XCTest helpers:
func XCTAssertThrowsSpecificError<ReturnValue, ExpectedError>(
file: StaticString = #file,
line: UInt = #line,
_ codeThatThrows: #autoclosure () throws -> ReturnValue,
_ error: ExpectedError,
_ message: String = ""
) where ExpectedError: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message, file: file, line: line) { someError in
guard let expectedErrorType = someError as? ExpectedError else {
XCTFail("Expected code to throw error of type: <\(ExpectedError.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
return
}
XCTAssertEqual(expectedErrorType, error, line: line)
}
}
func XCTAssertThrowsSpecificError<ExpectedError>(
_ codeThatThrows: #autoclosure () throws -> Void,
_ error: ExpectedError,
_ message: String = ""
) where ExpectedError: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message) { someError in
guard let expectedErrorType = someError as? ExpectedError else {
XCTFail("Expected code to throw error of type: <\(ExpectedError.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
return
}
XCTAssertEqual(expectedErrorType, error)
}
}
func XCTAssertThrowsSpecificErrorType<Error>(
_ codeThatThrows: #autoclosure () throws -> Void,
_ errorType: Error.Type,
_ message: String = ""
) where Error: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message) { someError in
XCTAssertTrue(someError is Error, "Expected code to throw error of type: <\(Error.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
}
}
Reflection magic
Or you can take a look at my Gist here which does not make use of Equatable at all, but can "compare" any enums errors which are not conforming to Equatable.
Usage
Together with CombineExpectation you can now write unit tests of your Combine code and compare errors more easily!
I've easily found the answer actually but I keep the question open in case someone else needed it.
The Failure keyword in this case is just the associatedtype that comes with the Publisher protocol.
In this case I'm just getting the Failure type for the Publishers.Decode, that by default is just Error.

How to fix cannot convert value of type Result<SuccessType<A>, Error> to closure result type Result<Success, Failure> Swift

I'm trying to map over Swift's Result type to produce a new Result of a different type but it isn't type checking, and is throwing the error:
error: cannot convert value of type Result<ParseSuccess<B>, ParseError> to closure result type Result<Success, Failure>
I've tried using a switch on the result and matching on the .success and .failure but I get a similar type checking error. I've also tried specifying the input and return types explicitly with no luck.
I've replicated the code in other languages I'm a little more familiar with (F# and Kotlin) and it seems to type check there.
The swift compiler shows a sqiggly under this line:
return self.apply(input).map { result in ParseSuccess(transform(result.data), result.input) }
Where am I going wrong with this code?
import Foundation
class ParseError : Error {
let err: String
init(_ err: String) {
self.err = err
}
}
class ParseSuccess<A> {
let data: A
let input: Substring
init(_ data: A, _ input: Substring) {
self.data = data
self.input = input
}
}
class Parser<A> {
let fn: (Substring) -> Result<ParseSuccess<A>, ParseError>
init(_ fn: #escaping (Substring) -> Result<ParseSuccess<A>, ParseError>) {
self.fn = fn
}
func apply(_ input: Substring) -> Result<ParseSuccess<A>, ParseError> {
return self.fn(input)
}
func map<B>(_ transform: #escaping (A) -> B) -> Parser<B> {
return Parser { (input) in
return self.apply(input).map { result in
ParseSuccess(transform(result.data), result.input) }
}
}
}
For what it's worth, I'm sure this is probably not the best way to start writing a parser combinator, but I've gotten caught up in trying to figure out where I went wrong here!
Thank you
It's a weird behaviour of the unqualified used of Paraser in the line return Parser {. When a generic type is mentioned in its unqualified form, within the body of the generic type's declaration, the type parameters are inferred to be unchanged. For example, a function on Array<T> can return simply Array, and the T is implied to be the same as the value of T for self. Needless to say, I'm not a huge fan of this behaviour.
Changing it to return Parser<B> { ... solves the issue.
Here's how I found the issue. When working with generics errors, I always try to insert a ton of type annotations to "pin down" the data types, to confirm that the compiler and me are on the same page.
I first wrote:
func map<B>(_ transform: #escaping (A) -> B) -> Parser<B> {
return Parser { (input) in
return self.apply(input).map { result in
let newData: ParseSuccess<B> = transform(result.data)
return ParseSuccess(newData, result.input)
}
}
}
At which point the error changed to:
Untitled 2.swift:32:10: error: cannot convert return expression of type 'Parser<A>' to return type 'Parser<B>'
return Parser { (input) in
^~~~~~~~~~~~~~~~~~~~

Swift 5 Result type

In Swift 5 Apple introduced Result type. It's generic enum with two cases:
public enum Result<Success, Failure: Error> {
case success(Success), failure(Failure)
}
Personally I used to two separate completions in network calls success: Completion and failure: Completion, but from what I see now, Apple pushing us to use single completion with Result type and then inside perform switch. So what are advantages of this approach with Result? Because in a lot of cases I can just omit error handling and don't write this switch. Thanks.
You shouldn’t omit cases when Result is failure. You shouldn’t do it with Result and you shouldn’t do it with your closure for failure. You should handle errors.
Anyway, Result type was introduced for simplifing completion handlers. You can have single closure for handling success or failure (primary-opinion based if two separate closures are better or not). Also Result is designed for error handling. You can simply create your own enum conforming to Error and then you can create your own error cases.
Swift 5 introduced Result<Success, Failure> associated value enumeration[About] It means that your result can be either success or failure with additional info(success result or error object). Good practice is to manage error case and success case as atomic task.
Advantages:
Result is a single/general(generic)/full type which can be used for all yes/no cases
Not optional type
Forces consumers to check all cases
public enum Result<Success, Failure> {
case success(Success)
case failure(Failure)
}
To use it
//create
func foo() -> Result<String, Error> {
//logic
if ok {
return .success("Hello World")
} else {
return .failure(.someError)
}
}
//read
func bar() {
let result = foo()
switch result {
case .success(let someString):
//success logic
case .failure(let error):
//fail logic
}
}