
The power of Result types in Swift - ingve
https://www.swiftbysundell.com/posts/the-power-of-result-types-in-swift
======
Smaug123
I've seen this referred to as "railway programming", and there's a wealth of
explanation over at
[https://fsharpforfunandprofit.com/rop/](https://fsharpforfunandprofit.com/rop/).
In particular, the slide deck from the FP eXchange contains a diagram (slide
75 out of 154) with a happy path along the top, and a sad path along the
bottom, and at every stage when you `bind` a Result into an existing Result,
you have the opportunity to divert into the sad path. Neat little metaphor.

~~~
mpweiher
What I found interesting is that if you have a pipe/filter architectural
style, your happy path stays completely free of error handling, because when a
filter doesn't have a good result, it just doesn't pass any data to the next
filter in line. Done!

You can then centralize the error handling by having a "stderr"-like output on
your filters.

When you have a call/return architectural style, you need to return
_something_ , and thread the result along, making things more complicated.

~~~
lmm
I find this concept of a "stderr output" far more complicated. What's next, a
"controlling terminal"?

Values are much easier to reason about and inspect than functions, so it's
better to have simple functions that return complicated values than to
complicate the functions themselves.

~~~
mpweiher
> I find this concept of a "stderr output" far more complicated.

And you know this how?

> Values are much easier to reason about and inspect than functions

Right, which is why we want to avoid functions as much as possible.

> simple functions that return complicated values than to complicate the
> functions themselves

Fortunately, these filters aren't functions, so this doesn't complicate them,
it makes them simpler.

~~~
lmm
> And you know this how?

Professional experience.

> Fortunately, these filters aren't functions, so this doesn't complicate
> them, it makes them simpler.

Functions are at least restricted enough that you can sort of reason about
them. If these things are not even functions then there's no hope of ever
understanding them.

------
jordansmithnz
The reason I started using this pattern is one I didn’t see the article
mention:

With legacy style result callbacks, you’ll have both an optional value and an
optional error returned - this corresponds to four possible states. If I’m
using an API returning this, I have to think about two cases which probably
won’t happen (neither an error or value, or both an error and a value). I’d
like to think the API I’m using wouldn’t return those states, but since it’s
not typed as such then code I’m writing should probably handle those cases
gracefully.

By using a result type as per the article, it is well defined that only two
states are possible. This pattern is a really nice one, and is a great use of
Swift’s associated value enums.

~~~
zoul
To nitpick, the “both error and data” can happen, for example when the
connection drops in the middle of a large response. Probably doesn’t change
anything, but it’s good to know, maybe so that you don’t trap on an unexpected
combination.

------
hannofcart
It's a very well written article. I'm just a bit surprised there is no mention
of the origins of this paradigm as it originated in functional languages
(Maybe monad in Haskell).

~~~
dwaite
I think it’s closer to Either

~~~
KirinDave
Well, Either is a monad. Evolution of this style to avoid divergent nesting
depths (The Codoken:
[http://i.imgur.com/BtjZedW.jpg](http://i.imgur.com/BtjZedW.jpg)) is a monad,
even in the "railway" style of F#.

------
solidsnack9000
It seems like a major legacy of practical experience with FP in the 200x and
201x years is going to be result types.

~~~
lmm
Every decade mainstream programming culture finally picks up one feature from
ML.

~~~
solidsnack9000
Technology is spreading much more quickly than training so it makes sense.
ISWIM was written up in the fifties.

------
KirinDave
The tricky part of this approach comes when you extend it. Let's say we have
two functions and we'd like to combine them and return a third.

    
    
        typealias Handler1 = (Result<Data, LoadError> -> Void)
        typealias Handler2 = (Result<Address, IndexError> -> Void)
    
        func load(addr: Address, then handler: @escaping Handler1) { ... }
        func lookup(name: string, then handler: @escaping Handler2) { ... }
    
    
        // What would the type of Handler3 be?
        func loadDefault(then handler: @escaping Handler3) {
           lookup("default") { result in
               switch result {
               case .success(let address):
                   load(address) { result2 in
                       switch result2 {
                           case .success(let data):
                               // Happy path
                           case .failure(let error):
                               // We have a load error for handler3
                   }
               case .failure(let outerError):
                   // ERROR: We have a lookup error for handler3
               }
            }
         }
         // P.S., don't complain about syntax I really don't know swift :P
    

Handler3's type is actually the sum of LoadError and IndexError. This is sort
of a non-trivial problem for a lot of use cases of Either. Monadic and Railway
Either programming typically solve this problem by saying, "Fine well then
every single Either/Result in the chain needs to be of the same type, which
includes a unified error type." In this article's case, I guess we could
appeal to Swift's binding to FoundationKit to lift up NSError? Sure! That'll
compile! Maybe there is a Compound Type thing? It will probably compile but
it'll shape your handler in weird ways.

It still seems pretty unsatisfying. The point here was to make it easy to work
out what errors we should handle, but now we need an explosion of combination
types (unique to each call graph in this case, wow!) Modern Haskell, which
along with ML pioneered these style of, has some slick type machinery tricks
to deal with this that are not well supported yet (even type lists are not a
slam dunk because we need something that's order-insensitive). Most folks will
be required to make a _new_ ad-hoc sum type (like Result, but
EitherError<Error1, Error2>), and then have to pattern match on that to handle
errors.

But at the end of the day, even Haskell with all its principle has try/catch
statements in IO and an exception hierarchy. In fact, it's considered best
practice for production code to avoid giving the appearance that Either can
capture all your errors (since it seldom can):
[https://www.fpcomplete.com/blog/2016/11/exceptions-best-
prac...](https://www.fpcomplete.com/blog/2016/11/exceptions-best-practices-
haskell)

Which is not to say Either's (and Railway programming's) "Sum-type errors")
aren't a substantial improvement over special return values or Common
Lisp/Go's product-style return values. They are. But sadly they can't really
get you away from exception handling when you start interacting with the
"real" world as the examples here do. It's a win for things like HTTP
responses or decoding libraries.

~~~
wereHamster
You can improve that code by having a single error type. Either by returning
the same error from both handlers or having an error sum type which contains
both errors. You probably don't really care about the exact error that can be
thrown by either handler, you just need enough information to display a
sensible message to the user. NSError may be just enough for that purpose!

After doing that, the type of Handler3 can be had as Result<Tuple<Data,
Address>, NSError>, for example. And how do you combine the two results into
the Tuple? That's an applicative, I'm sure swift provides combinators to do
just that.

~~~
taeric
Didn't the post you are responding to say that? I suspect you didn't read past
the example.

The problem comes because you have to either use a single error for two parts
or deal with a tough explosion of types. No?

~~~
masklinn
There are known solutions to this problem: you can type-erase to a single
supertype (e.g. Error/NSError[0]) or convert from the third-party's type to a
first-party type[1] for instance.

[0]
[https://developer.apple.com/documentation/swift/error](https://developer.apple.com/documentation/swift/error)

[1] [https://boats.gitlab.io/failure/](https://boats.gitlab.io/failure/)

~~~
KirinDave
It's sort of amazing that you're replying to a post suggesting something I
suggested, by re-suggesting what all 3 posts prior to you suggested.

But as I've said, yeah you can do this. But it's unfortunate because it
destroys a lot of information that's useful in the type signature. Haskell
does this with exception hierarchies and some clever runtime casting attempts,
but even that has ergonomic issues.

What'd be really amazing is if we could get the totality checking of pattern
matching but also the composability of exception handlers, and that's what the
next generation of error handling primitives are trying to do.

------
krzat
Result will likely not appear in standard library, because it duplicates
functionality of do/try/catch and async/await

------
sghiassy
How would the explicit case of success/fail for an API response compare to the
Promise pattern?

~~~
sghiassy
Seems perhaps the Promise pattern is more conducive for composition,
aggregation, etc of multiple asyncs?

Or are they just two different problem domains?

------
_31
Another great article on swiftBySundell. I really enjoy these well written
deep dives into Swift.

------
m3kw9
This only pushes the responsibility down lower in the stack. Somewhere someone
would have to handle a state where we have data and error being nil at the
same time.

~~~
minfduck
No it doesn't. This article is specifically about how you can use the Swift
type system to prevent that from happening. You get a compile time guarantee
that the result will either be data or error.

~~~
m3kw9
The server can a aboulutly send you data and error as nil, either by error or
on purpose. Someone is gonna handle that.

------
khitchdee
While typing is a good way to reduce programmer errors, specially for large
scale programs, it's much like autocomplete in a word processor. It's cool to
have but also a bad habit to form. The quality of prgrams would improve if
such aids were reduced and the programmer was expected to form good
programming habits instead.

~~~
ryanpetrich
We should also remove airbags and seatbelts in automobiles. Surely then better
driving habits would form.

~~~
dep_b
Removing protective padding and helmets might help in American Football. Rugby
doesn't use them and it's actually a safer sport.

But apart from that: the guy is wrong of course! Working in JavaScript still
drives me crazy because I don't have decent autocomplete and no IDE warnings
I'm using an API wrong.

~~~
steve_adams_86
It's worth checking out Visual studio code. It is a fairly advanced JavaScript
IDE in my experience. Using it with TypeScript is amazing if you're willing to
put up with the overhead of compiling to JavaScript (though you can run
TypeScript without a compile step in the server using node-ts). These tools
make JavaScript a lot nicer for me, and actually pretty enjoyable!

