我试图编写一个简单的模块函数,它将表达式作为"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])
)的表达式作为函数编写,然后能够将该运算符动态定义为原子?
答案 0 :(得分:3)
为什么在匿名函数中存在
operator
变量绑定会导致c
被评估为函数?
代码c operator d
被c(operator(d))
解析为Code.eval_string
,就像在普通的Elixir中一样,这意味着c
和operator
都必须是函数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