I' working on a basic 2D CAD engine and the pipeline operator significantly improved my code. Basically several functions start with a point (x,y) in space and compute a final position after a number of move operations:
let finalPosition =
startingPosition
|> moveByLengthAndAngle x1 a1
|> moveByXandY x2 y2
|> moveByXandAngle x3 a3
|> moveByLengthAndAngle x4 a4
// etc...
This is incredibly easy to read and I'd like to keep it that way. The various x1, a1, etc. obviously have a meaning name in the real code.
Now the new requirement is to introduce exception handling. A big try/with around the whole operation chain is not enough because I'd like to know which line caused the exception. I need to know which argument is invalid, so that the user knows what parameter must be changed.
For example if the first line (moveByLengthAndAngle x1 a1) raises an exception, I'd like to tell something like "Hey, -90 is an invalid value for a1! a1 must be between 45 and 90!". Given that many operations of the same type can be used in the sequence it's not enough to define a different exception type for each operation (in this example I wouldn't be able to tell if the error was the first or the last move).
The obvious solution would be to split the chain in single let statements, each within its respective try/with. This however would make my beautiful and readable code a bit messy, not so readable anymore.
Is there a way to satisfy this requirement without sacrificing the readability and elegance of the current code?
(note. right now every moveBy function raises an exception in case of errors, but I'm free to change for ex. to return an option, a bigger tuple, or just anything else if needed).