Логическое условие в операторе "с" не работает - PullRequest
0 голосов
/ 30 мая 2020

У меня есть этот код:

  def edit(conn, params) do
    with m1 <- Repo.get(Model1, params["model1_id"]),
      m2 <- Repo.get(Model2, params["model2_id"]),
      !is_nil(m1) and !is_nil(m2)
    do
      # 1
      res = !is_nil(m1) and !is_nil(m2)
      IO.puts("***** res: #{res}")                              # ===> false

      IO.puts("***** m1: #{Kernel.inspect(m1)}")                # ===> prints a struct
      IO.puts("***** m1 is_nil: #{is_nil(m1)}")                 # ===> false

      IO.puts("***** m2: #{Kernel.inspect(m2)}")                # ===> nil
      IO.puts("***** m2 is_nil: #{is_nil(m2)}")                 # ===> true

    else
      #2
      _ -> raise ArgumentError, "not found"
    end
  end

Поток №1 выполняется, хотя m2 равен нулю. Как это может быть? Как это исправить? Цель - убедиться, что m1 и m2 не равны нулю, а затем выполнить поток №1.

Ответы [ 3 ]

2 голосов
/ 30 мая 2020

Kernel.SpecialtForms.with/1 «ранний возврат», если и только в предложении не было совпадений.

В третьем предложении у вас есть !is_nil(m1) and !is_nil(m2), что примерно означает _ <- !is_nil(m1) and !is_nil(m2) и он совпадает, несмотря ни на что. Для достижения желаемого вам необходимо явно использовать правильное предложение with с <-:

with m1 <- Repo.get(Model1, params["model1_id"]),
     m2 <- Repo.get(Model2, params["model2_id"]),
     true <- !is_nil(m1) and !is_nil(m2), do: ...

Более естественным было бы использовать соответствующие меры защиты для раннего возврата ошибок:

with m1 when not is_nil(m1) <- Repo.get(Model1, params["model1_id"]),
     m2 when not is_nil(m2) <- Repo.get(Model2, params["model2_id"]),
       do: ...

На самом деле, with/1 здесь не нужен. Это подойдет идеально (спасибо, что nil было falsey):

if Repo.get(Model1, params["model1_id"]) &&
   Repo.get(Model2, params["model2_id"]), do: ...
0 голосов
/ 31 мая 2020

Я использую with только в определенных c ситуациях, и в них это действительно помогает, в основном, когда это некоторый набор операций, напоминающих «трубу», например, вам нужны результаты из предыдущих шагов в следующий, но они действительно разнородны, и у вас нет или не имеет смысла создавать какую-либо структуру токенов для хранения преобразований и ошибок (аналогично набору изменений ecto).

В тех случаях, если вы знаете необходим шаг с ошибкой. Я считаю, что упаковка операторов with в помеченный кортеж помогает, так как тогда вы можете сопоставить указанный тег c с ошибкой. Кроме того, вы на самом деле не используете код idiomati c, потому что вы используете with просто как выражение присваивания, если вы используете сопоставление с образцом, он становится более читабельным и, на мой взгляд, более идиоматическим c. В вашем примере это будет означать:

with {_, %Model1{} = m1} <- {Model1, Repo.get(Model1, params["model1_id"])},
     {_, %Model2{} = m2} <- {Model2, Repo.get(Model2, params["model2_id"])}
       do
          # we have both m1 and m2 and they are respectively instances of Model1 and Model2
          # do something with them
          {:ok, {m1, m2}}
else
     {Model1, _} -> 
            #failed fetching Model1
            {:error, :no_model1}
     {Model2, _} ->
            #failed fetching Model2
            {:error, :no_model2}
end

Соответствие шаблонов именно той структуре, которую вы хотите, и зная, что Repo.get вернет либо структуру схемы, либо nil, делает это так, что вам не нужно проверьте, является ли он nil, если это не структура схемы, он будет равен nil (если вы не используете Repo.get с предложением select, где вы возвращаете что-то еще из c).

Помните, что доступ к params["some_key"] может вернуть nil, и это вызовет исключение при попытке выполнить Repo.get, поэтому вы можете добавить еще два оператора с условиями для идентификатора, а также позволить вам вернуть идентификатор в случае, если они не были найдены, предполагая что идентификаторы являются числовыми идентификаторами (если двоичное изменение is_integer на is_binary):

with {_, id1} when is_integer(id1) <- {:id1, Map.get(params, "model1_id")},
     {_, id2} when is_integer(id2) <- {:id2, Map.get(params, "model2_id")},
     {_, _, %Model1{} = m1} <- {Model1, id1, Repo.get(Model1, id1)},
     {_, _, %Model2{} = m2} <- {Model2, id2, Repo.get(Model2, id2)}
       do
          # we have both m1 and m2 and they are respectively instances of Model1 and Model2
          # do something with them
          {:ok, {m1, m2}}
else
     {id_type, id_value} when id_type in [:id1, :id2] ->
            # one of the id params wasn't an integer
            {:error, {:unexpected_id, id_type, id_value}}
     {Model1, id, _} -> 
            # failed fetching Model1
            {:error, {:no_model1, id}}
     {Model2, id, _} ->
            # failed fetching Model2
            {:error, {:no_model2, id}}
end

Вероятно, есть лучшая обработка параметров, которые должны быть выполнены, например, их проверка перед достижением этого этапа выполнения , и если это будет сделано, вы, вероятно, могли бы go с оператором case, поскольку это всего 2 «случая»:

case Repo.get(Model1, valid_id_1) do
    %Model1{} = model1 ->

       case Repo.get(Model2, valid_id_2) do
           %Model2{} = model2 -> {:ok, {model1, model2}}
           nil -> {:error, {:no_model2, valid_id_2}}
       end

    nil -> {:error, {:no_model1, valid_id_1}}
end
0 голосов
/ 30 мая 2020

Выражение with

Выражение with используется строго для сопоставления с образцом. Это не «цепная замена» для условных выражений if-else.

Обычно with будет go проходить через все ваши предложения и пытаться сопоставить их по шаблону с левой стороной стрелки <-. Он выполнит только одно из предложений error , когда первое сопоставление с шаблоном не будет выполнено (не совпадает).

Проблема с вашим кодом

Третья строка в with равно !is_nil(m1) and !is_nil(m2), что всегда успешно соответствует шаблону, даже если само выражение равно false.

Исправление

Чтобы код выполнял то, что вы действительно хотите, вы должны добавить левая часть третьей строки, чтобы она была принудительно сопоставлена ​​с шаблоном:

with m1 <- Repo.get(Model1, params["model1_id"]),
      m2 <- Repo.get(Model2, params["model2_id"]),
      {false, false} <- {is_nil(m1), is_nil(m2)} do
 ...

Идиоматия c Эликсир

Чтобы сделать код немного более идиоматическим c Эликсир, вы также можно использовать Guards , что разрешено как is_nil. В результате ваш код будет выглядеть так:

with m1 when not is_nil(m1) <- Repo.get(Model1, params["model1_id"]),
      m2 when not is_nil(m2) <- Repo.get(Model2, params["model2_id"]) do
 ...

Даже лучшая читаемость

Последний совет - всегда сосредотачиваться на удобочитаемости. Вы пишете свой код для чтения людьми, поэтому легче читать меньше всего, что происходит в строке.

Ваш код будет еще более читаемым, как: нужен with?

Ваш with ничего не делает, за исключением того, что m1 и m2 не являются nil. Это легко сделать с помощью case или if, так как здесь вам не нужно никакого сопоставления с образцом:

m1 = Repo.get(Model1, params["model1_id"])
m2 = Repo.get(Model2, params["model2_id"])

if !is_nil(m1) && !is_nil(m2) do
 ...
...