xji
xji

Reputation: 8247

How to refactor case statements with excessive nesting

In many situations I find myself needing to pattern match on both a struct itself and its fields, and proceed to perform some further actions when some intermediate results are not empty.

However, the struct itself might be nil in the first place. This resulted in me writing multiple nested matches, e.g.

experiment = Repo.get(Experiment, experiment_id)

case experiment do
  nil ->
    # Error 1

  _ ->
    case experiment.active do
      false -> # Error 2
      true -> 
        case Repo.all(assoc(experiment, :experiment_results)) do
          [] -> # Error 3
          results -> # Do stuffs
    end
end

Ideally I would want to write code without so much nesting.

How can I refactor the code?


(Note that my original question was about pattern matching on a struct when it could be nil. My actual use case is broader than my original question suggested, thus I updated the question.)

The original code, to which AlekseiMatiushkin and Sheharyar's answers apply:

experiment = Repo.get(Experiment, experiment_id)

case experiment do
  nil ->
    :error

  _ ->
    case experiment.active do
      false -> :error
      true -> # Do stuffs
    end
end

Upvotes: 0

Views: 311

Answers (3)

xji
xji

Reputation: 8247

Another way is to use the with syntax. It essentially stipulates what the "happy path" should be, even when the path entails multiple relatively complex checks:

with experiment <- Repo.get(Experiment, id),
     {:nil_experiment, false} <- {:nil_experiment, is_nil(experiment)},
     experiment_results <- Repo.all(assoc(experiment, :experiment_results)),
     {:empty_results, false} <- {:empty_results, Enum.empty?(experiment_results)} do
do
  # Do stuffs with `experiment_results`
else
  {:nil_experiment, true} ->
     # Error message 1

  {:empty_results, true} ->
     # Error message 2

  _ ->
     # Unknown error
end

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

I would go with a direct pattern match.

Experiment
|> Repo.get(experiment_id)
|> case do
  %Experiment{active: true, other_attribute: :value} ->
    # do stuff
  _ ->
    :error
end

Upvotes: 5

Sheharyar
Sheharyar

Reputation: 75740

How about the && short-circuit operator?

if experiment && experiment.active && experiment.other do
  # do something
else
  :error
end

You can also use cond to add more cases:

cond do
  experiment && experiment.active && experiment.other ->
    # do something

  !experiment.active ->
    {:error, :inactive}

  is_nil(experiment) ->
    {:error, :experiment_is_nil}

  true ->
    {:error, :unknown}
end

Upvotes: 1

Related Questions