“ with”运算符中的逻辑条件不起作用

时间:2020-05-30 05:39:09

标签: elixir ecto

我有此代码:

  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

即使m2为零,流#1仍被执行。怎么可能?如何解决? 目标-确保m1和m2不为零,然后执行流程1。

3 个答案:

答案 0 :(得分:2)

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。这样做会很完美(感谢nilfalsey):

if Repo.get(Model1, params["model1_id"]) &&
   Repo.get(Model2, params["model2_id"]), do: ...

答案 1 :(得分:0)

with

with表达式严格用于模式匹配。它不是if-else条件的“可链接替代”。

基本上,with将遍历您的所有子句,并尝试将其与<-箭头的左侧进行模式匹配。当第一个模式匹配失败(不匹配)时,它将仅执行 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
 ...

成语长生不老药

要使代码更通用一些,您还可以使用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
 ...

可读性更好

最后一个技巧是始终专注于可读性。您正在编写供人类阅读的代码,因此,在线上发生的事件较少,通常更易于阅读。

您的代码将更具可读性:

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

with m1 when not is_nil(m1) <- m1,
      m2 when not is_nil(m2) <- m2 do
 ...

您实际上是否需要with吗?

您的with什么也不做,只是确保m1m2不是nil。也可以轻松地使用caseif完成此操作,因为这里实际上不需要任何模式匹配:

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

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

答案 2 :(得分:0)

我发现自己仅在特定情况下使用import React from "react"; import Autocomplete from "@material-ui/lab/Autocomplete"; export default function ComboBox() { return ( <Autocomplete id="combo-box-demo" options={top100Films} getOptionLabel={option => option.title} style={{ width: 300 }} renderInput={params => ( <div ref={params.InputProps.ref}> <label {...params.InputLabelProps}>My Label </label> <input {...params.inputProps} autoFocus /> </div> )} /> ); } ,在那些情况下它确实有帮助,主要是在一些类似于“管道”的操作集合中,例如,您需要下一个步骤的结果一个,但实际上是异构的,您没有或没有必要创建一些令牌结构来保存转换和错误(类似于ecto变更集)。

在这些情况下,如果需要知道失败的步骤,我会发现将with语句包装在带标签的元组中有帮助,因为这样您就可以匹配失败的特定标签。除此之外,您并没有真正使用惯用代码,因为您使用with只是一个赋值表达式,如果您使用模式匹配,那么它会变得更具可读性,并且在我看来也更加惯用。对于您的示例,这意味着:

with

模式与您想要的结构完全匹配,并且知道Repo.get将返回架构struct或nil,所以它使您不必检查它是否为nil,如果不是架构结构,它将保持为零(除非您将Repo.get与带有返回其他内容的select子句的selectc一起使用)。

请记住,访问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 可能会返回params["some_key"],并且在尝试执行nil时会抛出异常,因此您可以再添加两个带有id条件的语句,并启用如果未找到ID,则返回ID(假设ID是数字ID)(如果是二进制,请将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 语句,因为它只有2个“案例” :

case