> Complex else clauses in with<p>This has proven controversial, but sometimes the Else behavior is very sensitive to <i>which</i> of the With expressions failed (especially for better logging) and I maintain that the <i>least terrible</i> approach involves augmenting the With clauses using atoms to identify each expression when it fails, ex:<p>For example:<p><pre><code> with {_, {:ok, key} } <- {:phase_key, get_key() },
{_, {:ok, door}} <- {:phase_unlock, unlock(door) },
{_, %Thingy{} } <- {:phase_thingy, fetch_thingy(door)},
{_, %Widget{} } <- {:phase_widget, fetch_widget(door)} do
else
{:phase_key, {:error, _msg} = err} -> # I can tell the key was missing, rather than the lock broke
{:phase_unlock, {:error, _msg} = err} -> # I can tell the lock broke, rather than missing a key
{:phase_thingy, nil } -> # I can tell this nil value is a missing thingy, not a widget
{:phase_widget, nil } -> # I can tell this nil value is a missing widget, not a thingy
end
</code></pre>
As a big bonus, when something crazy-unexpected is emitted to cause a WithClauseError--like "hello world"--you have a much better chance of quickly diagnosing what happened from log messages, instead of inspecting a dozen different logical branching or trying to blindly repro it.<p>Pre-buttals:<p>"Just rewrite those 4 functions to return better somehow" -> No, that's not always practical or even possible. In the above example, the signatures of some of these functions are already perfectly fine <i>on their own</i>. The ambiguity comes when they get used near others in different ways, and trying to assign <i>globally</i> unique features to match on is a bad idea.<p>"Create 4 private functions to wrap things" -> Ugh, now the person reading the code has an even harder time, since they need to refer to functions elsewhere in the file, any of which might be hiding unexpected bonus behavior. Those 4 private functions might not even be useful in <i>other</i> with-statements in the same module, causing you to make even more private functions... and all of it still falls apart the moment your Else logging/cleanup needs to record or revert something from a prior (successful) step.