Elixir:在if语句中设置变量

时间:2016-09-17 19:02:18

标签: elixir

我对Elixir很新,这个简单的问题让我疯狂。

a = 0
if true do
    a = 1 + 1
end 
a = a + 1

IO.puts (a)

有趣的是,这给出了正确的值,但也给出了警告:

warning: the variable "a" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

case int do
  1 -> atom = :one
  2 -> atom = :two
end

should be written as

atom =
  case int do
    1 -> :one
    2 -> :two
  end

Unsafe variable found at:
  Untitled:5

3

我不理解警告信息。在Elixir中这样做的最佳方式是什么?

更新:这个条件怎么样?

a = 0
b = 0
if true do
    a = 1 + 1
    b = 2 + 2
end 
a = a + 1
b = b + 2

IO.puts (a)
IO.puts (b)

3 个答案:

答案 0 :(得分:15)

在Elixir中,每个语句都返回值。您可以将整个if语句值分配给变量,而不是在if中分配变量。

a = 0
a = if true do
      1 + 1
    else
      a + 1
    end

答案 1 :(得分:7)

警告是正确的,试图阻止你做_可能危险的事情。在Elixir的1.3更新日志中对此进行了很好的解释。

看一下命令式转让的弃用部分,在这里解释(带示例):

http://elixir-lang.org/blog/2016/06/21/elixir-v1-3-0-released/

希望有所帮助!

答案 2 :(得分:0)

我对Elixir还是很陌生,但也非常熟悉该家族中的两种语言的功能编程,并且更普遍地认为是样式的编程。基于宣布弃用此行为的博客文章[见下文],该意图似乎是鼓励采用更多惯用语言的编程(以及其他方法)。

避免这种行为的一个优点是,将这些代码块提取到一个单独的函数中变得更加简单。函数式编程风格鼓励将程序的行为视为一系列的数据转换或修改序列,而不是数据的修改。典型的函数式编程语言至少在默认情况下还提供不变的数据,同时在内部在程序生成的数据之间共享公用或共享的值,这鼓励您将数据转换实现为 pure 函数,即不会修改现有数据状态的代码块。

在Elixir和其他函数式编程语言中,模式匹配和强大的标准“集合”类型的组合提供了一种从单个函数或代码块中返回多个值的简洁明了的方法。相反,例如,在面向对象的编程语言中,通常会返回一个具有多个值的对象,该值可以作为该对象的类的成员进行访问。在Elixir或其他函数式编程语言中,这也是一种完全有效的模式-请参见Elixir中的structs-但它是不必要的,并且在返回相对较少数量的值时,几乎总是 clear 。< / p>

因此,您的第一个示例可以重写为:

a = 0
a = if true, do: 1 + 1, else: a
a = a + 1

IO.puts (a)

您的示例太虚构,以至于没有任何明显的优势。您的问题暗示了一种批评,我认为这是正确的,因为else(即需要明确地“禁止操作”更新a)是多余的。这是这种编程风格的真正的“固定成本”,尽管很小。但是您可以轻松地将“也许转换”行为的概念封装为一个函数或宏:

def maybe_transform(x, cond, f) do
  if cond, do: f.(x), else: x
end

通过多种可能的转换,可以更好地看到这种样式的真正好处:

a = 0

a
|> maybe_transform(cond1, &transform_function_1/1)
|> maybe_transform(cond2, &transform_function_2/1)
|> maybe_transform(cond3, &transform_function_3/1)

其中transform_function_1transform_function_2transform_function_3的函数将根据相关条件在(可能)并依次转换的值可能被调用a中的。请注意,|>运算符正在将a的(可能)转换后的值作为第一个参数传递给maybe_transform的每次调用。

您的第二个示例可以重写为:

a = 0
b = 0
{a, b} = if true, do: { 1 + 1, 2 + 2 }, else: {a, b}
a = a + 1
b = b + 2

IO.puts (a)
IO.puts (b)

同样,该示例的设计也非常繁琐,以至于使贬低命令式分配行为的好处不清楚。

a comment中当前接受的答案中,您写道:

  

如果要解决一个复杂的数学问题,需要使用多个嵌套的ifs来更改40个变量,那么我必须为每个嵌套的if语句定义{a,... a40}吗?

我想不出任何涉及40个“返回”变量的示例,这些示例中的计算或转换都取决于相同的复杂条件,而 以某种方式使命令式样式更清晰或更明显。详细,具体的示例将有所帮助。导致像40个值的矢量这样的数据的东西通常会被“结构化”,以使Elixir标准map模块中的reduceEnum函数在功能上通常更清晰无论是哪种编程风格,都比涉及命令式赋值的等效代码更是如此,并且您通常也不需要或不想为单个向量中包含的所有值维护40个单独的变量。

当我遇到这个问题时,我正在解决的问题涉及从两个可能的数据集构建一个列表。我的函数初稿:

def build_list(x) do
  new_list = []

  if cond1 do
    something = f1(x)

    if cond2 do
      new_list = [ f2(something) | new_list ]
    end
  end

  if cond3 do
    something_else = f3(x)

    if cond4 do
      something_completely_different = f4(something_else)

      if test(something_completely_different) do
        new_list = [ f5(something_completely_different) | new_list ]
      end
    end
  end

  new_list
end

我可以用多种方法来重写它,但我决定采用以下方法:

def build_list(x) do
  list_1 =
    case cond1 do
      false -> []
      true ->
        something = f1(x)
        if cond2, do: [f2(something], else: []
    end

  list_2 =
    if cond3 do
      something_else = f3(x)

      if cond4 do
        something_completely_different = f4(something_else)

        if test(something_completely_different) do
          something_completely_different
        else
          []
        end
      else
        []
      end
    else
      []
    end

  list_1 ++ list_2
end

请注意,新版本的行为有所不同,因为[x | list]返回了x内容中带有list 的新列表,而list_1 ++ list_2返回了list_2有效地附加list_1的新列表。就我而言,没关系。

而且,由于cond4test正在测试中,something_elsesomething_completely_different本身是空的,而list ++ [] == list却是空的,所以我最终得到了更多类似这样的东西:

def build_list(x) do
  list_1 =
    case cond1 do
      false -> []
      true ->
        something = f1(x)
        if cond2, do: [f2(something], else: []
    end

  list_2 =
    if cond3 do
      f4(f3(x))
    else
      []
    end

  list_1 ++ list_2
end

最终帮助我的部分原因是我在较新版本中使用的标准函数和运算符处理了“退化”数据,例如明智地使用一个空列表[]的值。我的cond4正在检查f3(x)不是一个空列表,但是给定一个空列表参数,f4本身可以正常工作,在这种情况下,它自己返回一个空列表。当使用语法[x | list]生成一个新列表时,x放在开头时,我必须检查x本身是否为空列表,否则它将附加一个空列表,如下所示:新列表的head元素,但是list ++ x为空时x ++ listlist都和x相同。

来自the official blog post announcing the release of Elixir version 1.3(引入了您所观察到的警告以将相关行为标记为已弃用的版本):

  

命令指定的弃用

     

现在,如果ifcase和朋友之类的结构分配给在外部作用域中访问的变量,Elixir将发出警告。例如,假设有一个称为格式的函数,它接收一条消息和一些选项,并且必须在消息旁边返回一个路径:

def format(message, opts) do
  path =
    if (file = opts[:file]) && (line = opts[:line]) do
      relative = Path.relative_to_cwd(file)
      message  = Exception.format_file_line(relative, line) <> " " <> message
      relative
    end

  {path, message}
end
     

上面的if块隐式地更改了message中的值。现在想象一下,我们想将if块移至其自己的函数以清理实现:

def format(message, opts) do
  path = with_file_and_line(message, opts)
  {path, message}
end

defp with_file_and_line(message, opts) do
  if (file = opts[:file]) && (line = opts[:line]) do
    relative = Path.relative_to_cwd(file)
    message  = Exception.format_file_line(relative, line) <> " " <> message
    relative
  end
end
     

重构版本已损坏,因为if块实际上返回了两个值,即相对路径新消息。 Elixir v1.3将在此类情况下发出警告,并强制从ifcase和其他构造中显式返回两个变量。此外,此更改使我们有机会在将来的发行版中统一语言范围规则。