Should one use pipes or with?

I use with for all pipelines where the functions can fail (i.e. return :ok or :error tuples) and we want to break out of the pipeline to deal with it and |> for simpler pipelines which either can never fail or throw when they do. This reduces the use of nested and confusing case or if statements.

Some contrived examples:

def build(params) do
  %User{}
  |> cast(params, [:name, :age])
  |> apply_changes()
end

def convert_to_csv(source_filename) do
    with {:l1, {:ok, :xlsx}} <- {:l1, detect_file_type(source_filename)},
         {:l2, {:ok, csv_filename}} <- {:l2, convert_excel_to_csv(source_filename)} do
  {:ok, csv_filename}
else
  {:l1, {:error, error}} -> {:error, error}  # (such as file not found)
  {:l1, {:ok, _other}} -> {:error, :not_an_excel_file}
  {:l2, error} -> error
end

Note wrapping the tuples in the with statement with their line numbers which I found necessary to disambiguate the else clauses. I’m not sure there’s a better way of doing that without a third-party library.

9 Likes