I find myself using a lot of very long with
-statements. The functions in the with
pipeline return {:ok, result}
or some {:error, :error_type, additional_error_information}
It could look somewhat like this:
with \
{:ok, result_a} <- function_a(input),
{:ok, result_b} <- function_b(result_a),
{:ok, result_c} <- function_c(result_b),
:ok <- some_validation_function_d(result_c)
do
{:ok, result_c}
else
{:error, :error_in_a} -> {:error, :internal_error}
{:error, :another_error_in_a} -> {:error, :some_other_error}
{:error, :error_in_b} -> {:error, :strange_error}
{:error, :error_in_b, msg} -> {:error, :strange_error, msg}
{:error, :error_in_c, msg} -> {:error, :internal_error}
{:error, :validation_error, _msg} -> {:error, :validation_error, input, msg}
end
But sometimes I have blocks that are way longer even.
This looks like a legacy of thinking like an imperative programmer to me, using a a (not so) nicely nested catch
-try
tree.
I would like to find a way to make my pipeline functions perform an âearly returnâ, so I would like to âbreakâ the pipeline.
It should look something like this instead (not working pseudocode following):
input
|> {function_a(), error_handler_a}
|> {function_b(), error_handler_b}
|> {function_c(), error_handler_c}
|> {function_d(), error_handler_d}
Where each error_handler
maps the error from the function to the correct error return I am using in this context. How do I do this best? I would be willing to write a macro (although I have no experience with this.) Another constraint: I do not want to use an external dependency for this, but be in full control of the code myself.
I am new to Elixir, and I can imagine that my idea of a solution does not make too much sense. If this is the case, what are other best practices to avoid the problem in the first place?