Turning to tuples everywhere, unfortunately, isn’t a viable solution. Just couple days ago, there was an issue about replacing the “awfully nil-ridden” Enum.find_value (and friends). This would mean, however, in many places replacing code that is very readable and simple with something very complex.
Let’s look at some examples I posted in the issue:
In Phoenix here we would change from:
content = Enum.find_value(sources, fn source -> File.exists?(source) && File.read!(source) end) || raise "could not find #{source_file_path} in any of the sources"to
res = Enum.seach(sources, fn source -> if File.exists?(source) do {:ok, File.read!(source)} else :error end end) content = case res do :error -> raise "could not find #{source_file_path} in any of the sources" {:ok, content} -> content end
Another similar use case is present just couple lines lower in the same file. I also found a couple more uses with an
||afterfind_value/2in Phoenix, but those could be arguably replaced by passing the default value tofind_value/3. It’s not possible in case we wanted to raise, though.
Another use that would require more convolution would require changing from:
Enum.find_value(headers, fn({k, v}) -> k =~ ~r/^origin$/i && v end)to
Enum.search(headers, fn({k, v}) -> if k =~ ~r/^origin$/i, do: {:ok, v}, else: :error end end)with additional modification of the consumer, from a simple
ifto acasematch on the ok tuple.
Last use I found after a brief grep of deps of my app is in ecto (and that one I wrote myself). It would require changing:
defp check_operations_valid(operations) do Enum.find_value(operations, &invalid_operation/1) || {:ok, operations} end
defp invalid_operation({name, {:changeset, %{valid?: false} = changeset, _}}),
do: {:error, {name, changeset, %{}}}
defp invalid_operation(_operation),
do: nilto ```elixir defp check_operations_valid(operations) do case Enum.search(operations, &invalid_operation/1) do :error -> {:ok, operations} {:ok, invalid} -> {:error, invalid} end end
defp invalid_operation({name, {:changeset, %{valid?: false} = changeset, _}}),
do: {:ok, {name, changeset, %{}}}
defp invalid_operation(_operation),
do: :error
While it does not introduce more code, it’s much harder to understand, since we’re looking for an error, but have to return it tagged in an
oktuple, just to switch tags at the end.
The situation is far more nuanced than “nil is bad”. I urge some of you that propose removing it, to try to remove it from your code and get back to us with results.
Also, switching from atom nil to [] would make the issue far, far worse. An empty list is so much more ubiquitous than atom nil, it would require that much more attention when used. It would also lead to conflating two notions - empty collections and “no value”.






















