I am using Fluture to abstract Futures.
Let's say I have a function that makes a GET request. This function can succeed or fail.
Upon making a request, if it succeeds, it prints a message, if it fails, it logs the error and executes a command.
axios.get(endpoint, { timeout: timeoutMs })
.fold(
err =>
logger.errorAsync( err )
.chain( ( ) => cmd.getAsync("pm2 restart app")),
response => logger.infoAsync( "Great success!" )
);
I have been reading the API, and I found that bimap
and fold
both apply a function to success and error:
bimap: Maps the left function over the rejection value, or the right function over the resolution value, depending on which is present.
fold: Applies the left function to the rejection value, or the right function to the resolution value, depending on which is present, and resolves with the result.
If you have a keen eye, you will know my example doesn't work. I need to use bimap
, but I don't get why.
bimap
and when should I use fold
?Let's first examine their respective type signatures:
bimap :: (a -> c) -> (b -> d) -> Future a b -> Future c d
fold :: (a -> c) -> (b -> c) -> Future a b -> Future d c
The difference is quite subtle, but visible. There are two major differences:
bimap
, both
functions are allowed to return different types. In fold
, both functions
must return a value of the same type.bimap
, you get back a Future where
the rejection contains a value of the type returned from the left function,
and the resolution contains a value of the type returned from the right
function. In fold
, the rejection side contains a whole new type variable that
is yet to be restricted, and the resolution side contains a value of the
type returned by both function.That's a quite a mouthful, and possibly a bit difficult to parse. I'll try to visualize it in diagrams.
For bimap
, it looks like the following. The two branches don't interact:
rej(x) res(y)
| |
| |
bimap(f)(g): f(x) g(y)
| |
V V
For fold
, the rejection branch kind of "stops", and the resoltion branch will
continue with the return value from f(x)
or the return value from g(y)
:
rej(x) res(y)
| |
| |
fold(f)(g): -> f(x)*g(y)
|
V
You can use bimap
whenever you'd like to change the rejection reason and the
resolution value at the same time. Doing bimap (f) (g)
is like doing
compose (mapRej (f)) (map (g))
.
You can use fold
whenever you want to move your rejection into the resolution
branch. In your case, this is what you want. The reason your example doesn't
work is because you end up with a Future of a Future, which you have to
flatten:
axios.get(endpoint, { timeout: timeoutMs })
.fold(
err =>
logger.errorAsync( err )
.chain( ( ) => cmd.getAsync("pm2 restart app")),
response => logger.infoAsync( "Great success!" )
)
.chain(inner => inner); //<-- Flatten
Flattening a Monad is very common in Functional Programming, and is typically
called join
, which can be implemented like:
const join = chain(x => x)