Сравнение с нулевым значением возвращает true

#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