Closed imcotton closed 4 years ago
This already exists. It is called chainFirst
. It is used as follows
import { pipe } from 'fp-ts/lib/pipeable'
import { right, fold } from 'fp-ts/lib/Either'
import { chainFirst } from 'fp-ts/lib/IO'
import { log, error } from 'fp-ts/lib/Console'
pipe(
( ) => right(42),
chainFirst(fold(error, log)),
) ( );
@cyberixae I am still confused about chainFirst. How would you explain it to beginners? Thank you!
@steida Read the introduction chapter of IO module. https://gcanti.github.io/fp-ts/modules/IO.ts.html Let me know if it helps. I wrote it in the hopes it would help beginners understand how this works. I can clarify further if it remains unclear but that is a good place to start reading about it.
I perfectly understand the IO module. The chainFirst is a mystery.
It is easy to understand by comparing the type signatures of chain
and chainFirst
. The difference is which result you want to return. If you'd use chain
for logging you'd be passing forward the undefined
(or void
) value B
returned by the console.log
call. Using chainFirst
solves this by throwing away the undefined
and passing forward A
, the value it received as input.
export declare const chain: <A, B>(f: (a: A) => IO<B>) => (ma: IO<A>) => IO<B>
export declare const chainFirst: <A, B>(f: (a: A) => IO<B>) => (ma: IO<A>) => IO<A>
@cyberixae Thanks for introducing chainFirst
, it's true that side effect logging should be inside of IO
, however lifting the entire pipe
by IO<Either>
seemed adding too much weights to it, no?
@imcotton it depends to what degree you wish to pretend that the logging does not happen. One key advantage of fp-ts is having absolutely everything visible in the type. From that perspective lifting the type to IO is the only option. I would agree that a different set of tools would be useful for reverse-engineering and debugging runtime code. However, I would be hesitant of adding such unsafe tools to fp-ts. I think it would be better to have a separate package for such tools.
The call at the end of the pipe executes the code and breaks referential transparency. I'm hoping to avoid such calls to some degree when I'm writing functional code. I created a separate package called ruins-ts to make the operations that ruin type safety more obvious creating a clear barrier between functional and non-functional code. With ruins the chainFirst example from above would look as follows.
import { pipe } from 'fp-ts/lib/pipeable'
import { right, fold } from 'fp-ts/lib/Either'
import { chainFirst } from 'fp-ts/lib/IO'
import { log, error } from 'fp-ts/lib/Console'
import * as ruins from 'ruins-ts'
ruins.fromIO(pipe(
( ) => right(42),
chainFirst(fold(error, log)),
));
Technically the package doesn't do much but using the package makes it possible to search your code base for architectural pain points. I think it would also be beneficial to have runtime debugging and reverse engineering tools in a separate package making it easier to find and eliminate or isolate such calls.
I appreciate the explanation, indeed the type is the fundamental building blocks in FP context.
Also this ruins-ts
surly looks interesting, will give it a try afterwards, thanks @cyberixae !
import { pipe } from 'fp-ts/lib/pipeable' import { right, fold } from 'fp-ts/lib/Either' import { chainFirst } from 'fp-ts/lib/IO' import { log, error } from 'fp-ts/lib/Console' pipe( ( ) => right(42), chainFirst(fold(error, log)), ) ( );
@cyberixae I do not think it's too generic, right? Eg in rxjs Tap works with any type.
Also in your example you have to use fold ffor both results, while usually people use either tap or tapLeft. Both R & L I would name biTap
@Lonli-Lokli You can use stuff from IOEither
if you wish to deal with left and right separately.
import { pipe } from 'fp-ts/lib/function'
import { right, chainFirstIOK, orElseFirst, fromIO } from 'fp-ts/lib/IOEither'
import { log, error } from 'fp-ts/lib/Console'
pipe(
right(42),
chainFirstIOK(log),
orElseFirst((e) => fromIO(error(e))),
)( );
🚀 Feature request
Current Behavior
Desired Behavior
Who does this impact? Who is this for?
All users.
Your environment