#elixir
#elixir
Вопрос:
Похоже, что следующее выражение вычисляется как "true"
:
if nil["nonexistent"] > 0.7 do "true" else "false" end
Каков идиоматический способ предотвратить этот тип ошибки? В моем случае у меня есть карта, которая может быть, а может и не быть nil
.
Ответ №1:
Есть много способов сделать это, и я не думаю, что какой-либо из них является особенно «идиоматичным» способом сделать это. В большинстве случаев я бы просто удостоверился в типе карты или значении ключа, используя защиту или сопоставление с шаблоном.
if is_float(mymap?["maybe_existent"]) do
mymap?["maybe_existent"] > 0.7
else
false
end
или
if is_map(mymap?) do
mymap?["nonexistent"] > 0.7
else
false
end
или
case mymap? do
%{"maybeexistent" => existent} when is_number(existent) ->
existent > 0.7
_ ->
false
end
или
with %{"existent" => existent} when is_number(existent) <- mymap? do
existent > 0.7
else
_ -> false
end
или
def f(%{"existent" => existent), do: existent > 0.7
def f(_), do: false
Я думаю, что в целом такого рода проблемы возникают, когда несколько промежуточных этапов обработки объединяются в одну функцию. Вам часто захочется разбить свою логику для работы с вашими данными в конвейерах небольших процессов (я говорю о «конвейерах» абстрактно, не обязательно конвейерах Elixir, сформированных с помощью |>
макроса), и, конечно, нет ничего плохого в том, чтобы разбить некоторую логику на приятные частные вспомогательные функции.
Так, например, у вас может быть список данных, которые могут быть картами, а могут и не быть ими (возможно, это могут быть nils или что-то совсем другое). Вы должны разделить этот вопрос на отдельную часть конвейера процесса, чтобы, когда придет время проверять значение вашего ключа, вы уже точно знали, с какими значениями вы работаете:
things = [%{a: 1}, nil, %{a: 3}, nil, nil, %{a: 0}]
things
|> Stream.filter(amp;is_map/1)
|> Stream.filter(amp; is_number(amp;1.a))
|> Stream.filter(amp; amp;1.a > 0.7)
|> Enum.to_list()
# results in: [%{a: 1}, %{a: 3}]
Это язык с динамической типизацией, поэтому вы можете по своему усмотрению решать, насколько тщательно вы хотите проверять возможные несоответствующие типы или отсутствующие данные. В предыдущем примере возможно, что ключ :a
не существует на карте, и мы не проверяли это, вместо этого мы решили просто предположить, что он есть, если мы уже знаем, что работаем над картой. Возможно, вам следует обеспечить более строгие проверки здесь. Это зависит от вас. Вы могли бы сделать это, используя другой фильтр или, возможно, используя четко определенные структуры вместо карт.
Или, может быть, вы вообще не начинаете с сбора данных, а вместо этого вам просто передается одна точка данных (возможно, из чего-то внешнего), и вам нужно проверить значение ключа на нем, затем вы можете передать его через ряд определенных функций, которые его преобразуют:
def handle_in(:create_thing, %{"thing" => mymap?}, socket) do
if thing_worthy?(mymap?) do
thing = make_thing(mymap?)
{:reply, {:ok, %{thing: thing}}, socket}
else
{:reply, {:error, :create_thing_fail}, socket}
end
end
defp thing_worthy?(mymap) do
mymap
|> my_guarantee_key("existent")
|> my_exceeds_threshold?(0.7)
end
Но в противном случае я бы, вероятно, просто использовал инструкцию case
или if
с парой контрольных проверок, аналогично одному из первых примеров выше.
Комментарии:
1. Спасибо за ответ. Все имеет смысл, за исключением третьего примера (использование
with
). Не могли бы вы уточнить это?2. Ах, я думаю, что я понял. Совпадаем ли мы с шаблоном по значению
mymap
?3. Лучше всего иметь значение по умолчанию «false» в форме with, на случай, если
mymap
это не карта илиexistent
не число
Ответ №2:
Я хотел бы предложить альтернативное решение: использовать Map.get
вместо []
. Хотя []
может индексировать nil
возвращаемый nil
, Map.get
(и другие Map
функции) в этих случаях произойдет сбой, что позволит вам обнаружить ошибку:
iex(1)> Map.get(nil, "key")
** (BadMapError) expected a map, got: nil
(elixir 1.10.3) lib/map.ex:450: Map.get(nil, "key", nil)
iex(1)> nil["key"]
nil
Затем, если сбой — это не то, что вы хотите, вы можете использовать ||
, чтобы убедиться, что первым аргументом является map и предоставить некоторое значение по умолчанию:
iex(2)> x = nil
iex(3)> Map.get(x || %{}, "key", 0) > 7
false