2017-10-09 77 views
0

我正试图编写一个简单的模块函数,它将一个表达式看作一个字符串,如"123 + 45"并返回一个答案。所以:Elixir:动态中缀运算符

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])之类的东西。 。似乎是有效的语法

所以我的第二个问题是:

是否可以写一个表达中缀运算符,如+的功能,然后才能够做动态定义操作员原子?

回答

2

Why did the presence of the operator variable binding in the anonymous function cause c to be evaluated as a function?

代码c operator dCode.eval_string解析为c(operator(d)),就像它会在正常药剂,这意味着两个coperator必须是元数1的功能该表达式是有道理的。你不会期望下面的代码可以工作,你会吗?

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

Is it possible to write an expression with an infix operator such as + as a function, and then to be able do dynamically define that operator as an atom?

由于这些运营商都只是Kernel模块定义的函数,你可以用apply/3叫它:

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

所以在你的原代码,只需用apply(Kernel, operator, [c, d])取代c operator 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