1

Let's assume a function is called when mapping an array, and this function returns a Result. For example:

func parseFeedItem(_ object: Any) -> Result<FeedItem, Error> {...}

func parseFeed(_ root: Any) -> Result<Feed, Error> {
    ...
    let items = objects.compactMap { parseFeedItem($0) }
    ...
}

The result of the map (items above), will be an array of Results. Ideally, following this, we'd want to know the "union" of results, that is a Result as either:

  • success with an array of values, if all Results in the array are successes; or
  • failure with the error of the first failure in the array.

Basically, I came up with:

typealias Results<T, E> = Result<[T], E> where E: Error

let r: Results<FeedItem, Error> = items.reduce(.success([FeedItem]())) { (accumulator, result) -> Results<FeedItem, Error> in
    switch accumulator {
    case .failure(_):
        return accumulator
    case .success(let array):
        switch result {
        case .failure(let error):
            return .failure(error)
        case .success(let value):
            var newArray = array
            newArray.append(value)
            return .success(newArray)
        }
    }
}

For example, if run with only successes:

let items: [Result<FeedItem, Error>] = [
    .success(FeedItem(1)),
    .success(FeedItem(2)),
    .success(FeedItem(3)),
]
... run `reduce` defined above ...
print(r) // .success([FeedItem(1), FeedItem(2), FeedItem(3)])

And if the array contains at least one failure, like:

let items: [Result<FeedItem, Error>] = [
    .success(FeedItem(1)),
    .success(FeedItem(2)),
    .failure(MyError.blah),
]
... run `reduce` defined above ...
print(r) // .failure(MyError.blah)

So this works. However, here are the questions:

  • is there a shorter way to do this?
  • is it performant, i.e. is there any way this could be improved?
2
  • 3
    Maybe you should post this at codereview.stackexchange.com since you don't have an issue with your code. Commented Jul 24, 2020 at 16:16
  • Didn't know about codereview.stackexchange.com. Thanks for the suggestion! Commented Aug 3, 2020 at 12:13

1 Answer 1

3

There is a shorter and generic way to accomplish this. The Result type has two very useful methods: map() and flatMap(), see the linked Apple documentation to know exactly how they work.

Using them I built this generic function that allows you to perform custom operations between types wrapped in a Result, giving .success (and the result of the operation) if both the members of the operation are a success, or failure the first time it encounters an error :

func resultOperation<T, E: Error>(_ lhs: Result<T, E>, _ rhs: Result<T, E>, _ operation: ((T, T) -> T)) -> Result<T, E> {
    lhs.flatMap { (lhsValue) -> Result<T, E> in
        rhs.map { (rhsValue) -> T in
            return operation(lhsValue, rhsValue)
        }
    }
}

In your case the operation parameter would be the + operator, which adds the elements of two arrays in a single array. The code would look like:

let r: Results<FeedItem, Error> = items.reduce(.success([FeedItem]())) { resultOperation($0, $1, +) }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.