Elixir:在if语句中设置变量

iph*_*aaw 7 elixir

我是Elixir的新手,这个简单的问题让我疯狂.

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

IO.puts (a)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

我不明白警告信息.在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)
Run Code Online (Sandbox Code Playgroud)

Pat*_*wak 15

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

a = 0
a = if true do
      1 + 1
    else
      a + 1
    end
Run Code Online (Sandbox Code Playgroud)

  • 然后你可以使用模式匹配`{a, b} = if true do {a + 1, b + 2} else {a, b} end` (3认同)
  • 考虑一下你到底想用这个实现什么*。Elixir 和 Erlang 中的分支是通过许多函数子句和模式匹配来解决的,而不是 if 语句。 (2认同)
  • 我同意@PatNowak 的观点。如果您对 N 个变量执行类似的转换并且使用嵌套的 if,则最好使用递归和多个函数子句。 (2认同)

Paw*_*zak 8

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

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

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

希望有所帮助!


Ken*_*itt 5

我对 Elixir 很陌生,但也非常熟悉通过该家族中的两种语言进行的函数式编程,更一般地作为一种编程风格。根据宣布弃用此行为的博客文章 [见下文],其意图似乎是鼓励更惯用的编程(除其他外)。

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

在 Elixir 和其他函数式编程语言中,模式匹配和强大的标准“集合”类型的组合提供了一种从单个函数或代码块返回多个值的简洁明了的方法。相比之下,例如,在面向对象的编程语言中,通常会返回一个对象,该对象具有作为该对象类的成员可访问的多个值。这在 Elixir 或其他函数式编程语言中也是一个完全有效的模式——参见Elixir 中的结构——但是当返回相对较少的值时,它是不必要的,而且几乎总是不太清楚

所以你的第一个例子可以改写为:

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

IO.puts (a)
Run Code Online (Sandbox Code Playgroud)

你的例子太做作,没有任何明显的优势。你的问题暗示了批评,我认为这是有效的else,即需要明确“无操作”更新a,是多余的。这是这种编程风格的一个真实的,尽管是次要的,“固定成本”。但是您可以轻松地将“可能转换”行为的想法封装为函数或宏:

def maybe_transform(x, cond, f) do
  if cond, do: f.(x), else: x
end
Run Code Online (Sandbox Code Playgroud)

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

a = 0

a
|> maybe_transform(cond1, &transform_function_1/1)
|> maybe_transform(cond2, &transform_function_2/1)
|> maybe_transform(cond3, &transform_function_3/1)
Run Code Online (Sandbox Code Playgroud)

其中函数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)
Run Code Online (Sandbox Code Playgroud)

同样,这个例子是人为设计的,以致于使弃用命令式赋值行为的好处变得不清楚。

在对当前接受的答案的评论中,您写道:

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

我想不出任何涉及 40 个“返回”变量的例子,其中计算或转换都取决于相同的复杂条件,并且没有以某种方式进行结构化,从而使命令式风格更加清晰或者在某些方面显然更好。一个详细的、具体的例子会很有帮助。产生像 40 个值的向量这样的数据的东西通常会被“结构化”,这样Elixir 标准模块中的mapreduce函数Enum通常会比涉及命令式赋值的等效代码更清晰,无论如何,在函数式编程风格中,你不会'通常不需要或不想为单个向量中包含的所有值维护 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
Run Code Online (Sandbox Code Playgroud)

我可以通过多种方式重写它,但我最终选择了这样的方法:

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
Run Code Online (Sandbox Code Playgroud)

需要注意的是,新版本的不同表现为[x | list]回报新列表与x 前缀到的内容list,而list_1 ++ list_2返回一个新的列表 list_2有效地追加list_1。就我而言,这无关紧要。

而且,因为cond4andtest在实际中测试something_elseor something_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
Run Code Online (Sandbox Code Playgroud)

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

宣布发布 Elixir 1.3的官方博客文章(您观察到的警告被引入以将相关行为标记为已弃用的版本):

弃用命令式赋值

Elixir 现在会警告,如果像if,case和朋友这样的构造分配给在外部作用域中访问的变量。举个例子,想象一个名为 format 的函数,它接收一条消息和一些选项,它必须在消息旁边返回一个路径:

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

重构版本被破坏,因为该if块实际上返回了两个值,相对路径新消息。药剂V1.3将警告在这种情况下,迫使这两个变量从明确回来ifcase和其他结构。此外,此更改使我们有机会在未来版本中统一语言范围规则。