【问题标题】:Elixir: Dynamic infix operatorElixir:动态中缀运算符
【发布时间】:2017-10-09 23:51:09
【问题描述】:

我正在尝试编写一个简单的模块函数,它将表达式作为"123 + 45" 之类的字符串并返回一个答案。所以:

Calculator.calculate("123 + 45")
# => 168

我想只在(" ") 上拆分字符串以获取整数和运算符,然后基本上调用Code.eval_string 就可以了。这是我第一次天真的尝试:

defmodule Calculator do
  def calculate(str) do
    [x, oper, y] = String.split(str, " ")
    formula = "a = fn (c, d) -> c operator d end; a.(c, d)"
    {answer, _ } = Code.eval_string(formula, [
      c: String.to_integer(x),
      operator: String.to_atom(oper),
      d: String.to_integer(y)
    ])
    answer
  end
end

然后,在运行它时,我收到此错误:

** (CompileError) nofile:1: undefined function c/1
    (elixir) src/elixir_fn.erl:10: anonymous fn/3 in :elixir_fn.expand/3
    (stdlib) lists.erl:1239: :lists.map/2
    (elixir) src/elixir_fn.erl:14: :elixir_fn.expand/3

我不明白为什么 c 被评估为一个函数。我怀疑它与匿名函数中的operator 变量有关。我通过用硬编码运算符重写它确认了这一点:

defmodule Calculator do
  def calculate(str) do
    [x, _, y] = String.split(str, " ")
    formula = "a = fn (c, d) -> c + d end; a.(c, d)"
    {answer, _ } = Code.eval_string(formula, [
      c: String.to_integer(x),
      d: String.to_integer(y)
    ])
    answer
  end
end

确实,这会产生预期的结果。问题是:

为什么匿名函数中存在operator 变量绑定导致c 被评估为函数?

Code.eval_string 上的文档看来,变量绑定似乎可以是任何东西,只要它们在作为eval_string 的第二个参数的关键字列表中找到。

在我的第二次尝试中,我考虑尝试将运算符从输入字符串转换为原子,并将运算符从中缀转换为函数调用(即,从 1 + 3 转换为 1.(:+, [3]) 之类的东西。但那似乎不是有效的语法。

所以我的第二个问题是:

是否可以用+ 之类的中缀运算符作为函数编写表达式,然后将该运算符动态定义为原子?

【问题讨论】:

    标签: elixir


    【解决方案1】:

    为什么匿名函数中存在operator 变量绑定导致c 被评估为函数?

    Code.eval_string 将代码c operator d 解析为c(operator(d)),就像在普通Elixir 中一样,这意味着coperator 必须是arity 1 的函数,该表达式才有意义。您不会期望以下代码可以工作,对吗?

    c = 1
    operator = :+
    d = 2
    a = fn (c, d) -> c operator d end; a.(c, d)
    

    是否可以将带有中缀运算符(例如 +)的表达式编写为函数,然后能够将该运算符动态定义为原子?

    由于这些操作符只是Kernel模块中定义的函数,你可以使用apply/3来调用它:

    iex(1)> c = 1
    1
    iex(2)> operator = :+
    :+
    iex(3)> d = 2
    2
    iex(4)> apply(Kernel, operator, [c, d])
    3
    

    因此,在您的原始代码中,只需将 c operator d 替换为 apply(Kernel, operator, [c, d])

    这里也不需要eval_string

    iex(1)> [a, b, c] = String.split("123 * 456", " ")
    ["123", "*", "456"]
    iex(2)> a = String.to_integer(a)
    123
    iex(3)> b = String.to_atom(b)
    :*
    iex(4)> c = String.to_integer(c)
    456
    iex(5)> apply(Kernel, b, [a, c])
    56088
    

    【讨论】:

      猜你喜欢
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      • 2017-11-24
      • 2022-11-24
      • 2019-03-28
      • 2011-03-12
      • 1970-01-01
      相关资源
      最近更新 更多