问题 如何在保护条款中使用'in'运算符?


我想在Elixir写一个字谜检查器。它需要2个单词,第一个是参考,第二个是作为第一个可能的anagram测试。

我试图用递归和模式匹配来编写它。我收到有关使用该错误的错误 in保护条款中的操作员:

(ArgumentError)运算符的无效args,它需要编译   用于保护表达式时右侧的时间列表或范围

我不知道该怎么做才能解决它。这是代码(错误在第4个定义中):

defmodule MyAnagram do
  def anagram?([], []), do: true

  def anagram?([], word) do
    IO.puts 'Not an anagram, the reference word does not contain enough letters'
    false
  end

  def anagram?(reference, []) do
    IO.puts 'Not an anagram, some letters remain in the reference word'
    false
  end

  def anagram?(reference, [head | tail]) when head in reference do
    anagram?(reference - head, tail)
  end

  def anagram?(_, [head | _]) do
    IO.puts 'Not an anagram, #{head} is not in the reference word.'
    false
  end
end

10385
2017-07-02 12:42


起源



答案:


这是由以下代码引起的(如您所示):

def anagram?(reference, [head | tail]) when head in reference do
  anagram?(reference - head, tail)
end

你可以找到的定义 in 宏 在源代码中,但为了方便起见,我在这里复制了它 - 它还包含文档中的以下内容:

逆天

in 运算符可以在保护条款中使用   因为右侧是范围或列表。在这种情况下,Elixir   将运算符扩展为有效的保护表达式。例如:

  when x in [1, 2, 3] 

翻译为:

  when x === 1 or x === 2 or x === 3

定义宏的代码:

  defmacro left in right do
    in_module? = (__CALLER__.context == nil)

    right = case bootstraped?(Macro) and not in_module? do
      true  -> Macro.expand(right, __CALLER__)
      false -> right
    end

    case right do
      _ when in_module? ->
        quote do: Elixir.Enum.member?(unquote(right), unquote(left))
      [] ->
        false
      [h|t] ->
        :lists.foldr(fn x, acc ->
          quote do
            unquote(comp(left, x)) or unquote(acc)
          end
        end, comp(left, h), t)
      {:%{}, [], [__struct__: Elixir.Range, first: first, last: last]} ->
        in_range(left, Macro.expand(first, __CALLER__), Macro.expand(last, __CALLER__))
      _ ->
        raise ArgumentError, <<"invalid args for operator in, it expects a compile time list ",
                                        "or range on the right side when used in guard expressions, got: ",
                                        Macro.to_string(right) :: binary>>
    end
  end

您的代码块正在命中case语句的最后一部分,因为在编译时无法保证您的变量 reference 是类型 list (要么 range。)

您可以通过调用以下内容查看传递给宏的值:

iex(2)> quote do: head in reference                                       
{:in, [context: Elixir, import: Kernel],
 [{:head, [], Elixir}, {:reference, [], Elixir}]}

在这里,原子 :reference 正在传递给 in 宏,它与之前的任何条款都不匹配,所以它落到了 _ 子句(引发错误。)

要解决此问题,您需要将最后两个子句合并为一个函数:

  def anagram?(reference, [head | tail]) do
    case head in reference do
      false ->
        IO.puts 'Not an anagram, #{head} is not in the reference word.'
        false
      true ->
        anagram?(reference - head, tail)
    end
  end

值得注意的是,您可能想要使用 "strings" 代替 'char_lists'  http://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#char-lists

另一件事是打电话 reference - head 将无法工作(它将提出一个 ArithmeticError)。你可能想看一下 List.delete / 2 从列表中删除项目。


16
2017-07-02 13:09



谢谢 !你能详细说明你的最后2分吗?为什么我要使用“字符串”而不是字符列表?很好的抓住了 -,我打算用 -- 但我的手指很难解释这个计划。这适用于char列表,​​而不是字符串。 - svarlet
@svarlet通常,char_lists仅用于维护Erlang库的支持。 Elixir库通常更喜欢字符串,因为它们具有出色的UTF-8支持。我试图为你找到一个可靠的参考,但遗憾的是大多数资源都停留在“你应该使用字符串,除非与Erlang接口。” - Gazler
尽管char列表适用于IO,但你应该养成使用双引号的习惯,否则它肯定会在以后咬你。例如,String模块仅适用于双引号的字符串。 - José Valim


答案:


这是由以下代码引起的(如您所示):

def anagram?(reference, [head | tail]) when head in reference do
  anagram?(reference - head, tail)
end

你可以找到的定义 in 宏 在源代码中,但为了方便起见,我在这里复制了它 - 它还包含文档中的以下内容:

逆天

in 运算符可以在保护条款中使用   因为右侧是范围或列表。在这种情况下,Elixir   将运算符扩展为有效的保护表达式。例如:

  when x in [1, 2, 3] 

翻译为:

  when x === 1 or x === 2 or x === 3

定义宏的代码:

  defmacro left in right do
    in_module? = (__CALLER__.context == nil)

    right = case bootstraped?(Macro) and not in_module? do
      true  -> Macro.expand(right, __CALLER__)
      false -> right
    end

    case right do
      _ when in_module? ->
        quote do: Elixir.Enum.member?(unquote(right), unquote(left))
      [] ->
        false
      [h|t] ->
        :lists.foldr(fn x, acc ->
          quote do
            unquote(comp(left, x)) or unquote(acc)
          end
        end, comp(left, h), t)
      {:%{}, [], [__struct__: Elixir.Range, first: first, last: last]} ->
        in_range(left, Macro.expand(first, __CALLER__), Macro.expand(last, __CALLER__))
      _ ->
        raise ArgumentError, <<"invalid args for operator in, it expects a compile time list ",
                                        "or range on the right side when used in guard expressions, got: ",
                                        Macro.to_string(right) :: binary>>
    end
  end

您的代码块正在命中case语句的最后一部分,因为在编译时无法保证您的变量 reference 是类型 list (要么 range。)

您可以通过调用以下内容查看传递给宏的值:

iex(2)> quote do: head in reference                                       
{:in, [context: Elixir, import: Kernel],
 [{:head, [], Elixir}, {:reference, [], Elixir}]}

在这里,原子 :reference 正在传递给 in 宏,它与之前的任何条款都不匹配,所以它落到了 _ 子句(引发错误。)

要解决此问题,您需要将最后两个子句合并为一个函数:

  def anagram?(reference, [head | tail]) do
    case head in reference do
      false ->
        IO.puts 'Not an anagram, #{head} is not in the reference word.'
        false
      true ->
        anagram?(reference - head, tail)
    end
  end

值得注意的是,您可能想要使用 "strings" 代替 'char_lists'  http://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#char-lists

另一件事是打电话 reference - head 将无法工作(它将提出一个 ArithmeticError)。你可能想看一下 List.delete / 2 从列表中删除项目。


16
2017-07-02 13:09



谢谢 !你能详细说明你的最后2分吗?为什么我要使用“字符串”而不是字符列表?很好的抓住了 -,我打算用 -- 但我的手指很难解释这个计划。这适用于char列表,​​而不是字符串。 - svarlet
@svarlet通常,char_lists仅用于维护Erlang库的支持。 Elixir库通常更喜欢字符串,因为它们具有出色的UTF-8支持。我试图为你找到一个可靠的参考,但遗憾的是大多数资源都停留在“你应该使用字符串,除非与Erlang接口。” - Gazler
尽管char列表适用于IO,但你应该养成使用双引号的习惯,否则它肯定会在以后咬你。例如,String模块仅适用于双引号的字符串。 - José Valim