How about bending over a selection? Let's say that instead of pipelining to perform actions, you represent them as follows:
let startingPosition = 0. ,0. let moveByLengthAndAngle la (x,y) = x,y // too lazy to do the math let moveByXandY dx dy (x,y) = //failwith "oops" x+dx, y+dy let moveByXandAngle dx a (x,y) = x+dx, y let actions = [ moveByLengthAndAngle 0. 0., "failed first moveByLengthAndAngle" moveByXandY 1. 2., "failed moveByXandY" moveByXandY 3. 4., "failed moveByXandY" moveByXandAngle 3. 4., "failed moveByXandAngle" moveByLengthAndAngle 4. 5., "failed second moveByLengthAndAngle" ]
i.e. actions is of type ((float * float -> float * float) * string) list .
Now, using FSharpx , we pick up actions and add / bind ( not sure what to call it like foldM in Haskell ) over actions:
let folder position (f,message) = Choice.bind (Choice.protect f >> Choice.mapSecond (konst message)) position let finalPosition = List.fold folder (Choice1Of2 startingPosition) actions
finalPosition is of type Choice<float * float, string> , that is, it is either the final result of all these functions, or an error (as defined in the table above).
Explanation for this last snippet:
- Choice.protect is similar to Tomas protection, except that when it finds an exception, it returns the exception enclosed in Choice2Of2. When there is no exception, it returns the result enclosed in Choice1Of2.
- Choice.mapSecond changes this potential exception in Choice2Of2 with the error message defined in the action table. Instead of (konst message), it can also be a function that builds an error message using an exception.
- Choice.bind launches this "protected" action against the current position. It will not take a valid action if the current position is erroneous (i.e. Choice2Of2).
- Finally, the summary applies all the actions overlapping / accumulating the selection (either the current position or the error).
So, now we just need to match the template to handle each case (the correct result or error):
match finalPosition with | Choice1Of2 (x,y) -> printfn "final position: %f,%f" xy | Choice2Of2 error -> printfn "error: %s" error
If you uncomment failwith "oops" above, finalPosition will be Choice2Of2 "failed moveByXandY"