Open OliverJAsh opened 3 years ago
Interestingly RxJS (which also uses pipeable operators) doesn't suffer from the same problem, presumably because each operator is defined as a class instance. Not sure we'd want that though.
const { pipe } = require("fp-ts/function");
const Rx = require("rxjs");
const RxO = require("rxjs/operators");
const as = Rx.of(1);
const fn = () => {
throw new Error("oops");
};
pipe(as, RxO.map(fn)).subscribe();
Error: oops
at MapSubscriber.fn [as project] (/Users/oliverash/Development/fp-ts-stack-trace-test/Rx.js:7:9)
at MapSubscriber._next (/Users/oliverash/Development/fp-ts-stack-trace-test/node_modules/rxjs/internal/operators/map.js:49:35)
at MapSubscriber.Subscriber.next (/Users/oliverash/Development/fp-ts-stack-trace-test/node_modules/rxjs/internal/Subscriber.js:66:18)
at Observable._subscribe (/Users/oliverash/Development/fp-ts-stack-trace-test/node_modules/rxjs/internal/util/subscribeToArray.js:5:20)
at Observable._trySubscribe (/Users/oliverash/Development/fp-ts-stack-trace-test/node_modules/rxjs/internal/Observable.js:44:25)
at Observable.subscribe (/Users/oliverash/Development/fp-ts-stack-trace-test/node_modules/rxjs/internal/Observable.js:30:22)
at MapOperator.call (/Users/oliverash/Development/fp-ts-stack-trace-test/node_modules/rxjs/internal/operators/map.js:32:23)
at Observable.subscribe (/Users/oliverash/Development/fp-ts-stack-trace-test/node_modules/rxjs/internal/Observable.js:25:31)
at Object.<anonymous> (/Users/oliverash/Development/fp-ts-stack-trace-test/Rx.js:9:23)
Side note: I know exceptions are discouraged in FP, but inevitably they can still happen, e.g. if the types are wrong or you call a function which you didn't realise would throw.
I would prefer using named functions function x()
instead of const x = () =>
so that the change wouldn't hurt the readability of code but, in principle, I am not opposed to improving traces even though this should be considered a precautionary measure rather than a standard (APIs that throw should be wrapped in things like IOEither)
One concern would be to make sure the non-exported named functions dont get mangled by minification. I think by default non top level functions get mangled which would lead to no real gain here, right? Also, don't source maps provide you this information?
don't source maps provide you this information?
Hmm, source maps can't help if the function doesn't have a name. Reduced test case:
const map = () => () => 1;
map().name // => ''
One concern would be to make sure the non-exported named functions dont get mangled by minification.
That won't be an issue as long as you're using source maps.
🚀 Feature request
Current Behavior
When an exception happens inside of an operator, the stack trace is ambiguous: it does not say which operator the exception occurred inside of.
Observe the second entry in the stack:
This entry represents the function returned by
map
, which is an anonymous function with no name:Nothing in the stack trace tells you that this exception occurred during the execution of
map
.Desired Behavior
When an exception happens inside of an operator, the stack trace should not be ambiguous: it should say which operator the exception occurred inside of, similar to
Array.prototype.map
:Observe the second entry in the stack:
Suggested Solution
We can achieve similar behaviour with fp-ts behaviours if we provide explicit function names for the functions returned by operators such as
map
:The stack trace now appears like so:
Alternatively:
Full example for testing:
Who does this impact? Who is this for?
All users who care about the debugging experience when exceptions inevitably happen.
Describe alternatives you've considered
Additional context
Your environment