Elixir:动态中缀运算符

时间:2017-10-09 15:41:31

标签: elixir

我试图编写一个简单的模块函数,它将表达式作为"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上的文档中可以看出,只要它们在{{1}的第二个参数的关键字列表中找到,变量绑定就好像几乎任何东西一样。 }。

在我的第二次尝试中,我考虑过尝试将运算符从输入字符串转换为原子并将运算符从中缀转换为函数调用(即,从eval_string转换为1 + 3之类的内容但是,这似乎不是有效的语法。

所以我的第二个问题是:

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

1 个答案:

答案 0 :(得分:3)

  

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

代码c operator dc(operator(d))解析为Code.eval_string,就像在普通的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