Reputation: 6707
I'm trying to write a simple module function that takes an expression as a string like "123 + 45"
and returns an answer. So:
Calculator.calculate("123 + 45")
# => 168
I thought about just splitting the string on (" ")
to get the integers and the operator, then basically calling Code.eval_string
on it. Here was my first naive attempt:
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
Then, in running it, I receive this error:
** (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
I couldn't figure out why c
was being evaluated as a function. I suspect that it has to do with the operator
variable in the anonymous function. I confirmed that by rewriting it with a hardcoded 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
Indeed, this produces the expected result. The question is:
Why did the presence of the operator
variable binding in the anonymous function cause c
to be evaluated as a function?
It appears from the docs on Code.eval_string
makes it seem as though the variable bindings can be just about anything as long as they're found in the keyword list that is the second argument to eval_string
.
In my second attempt, I thought about trying to convert the operator from the input string into an atom and convert the operator from an infix to a function call (i.e., from 1 + 3
to something like 1.(:+, [3])
. But that doesn't appear to be valid syntax.
So my second question is:
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?
Upvotes: 0
Views: 456
Reputation: 222348
Why did the presence of the
operator
variable binding in the anonymous function causec
to be evaluated as a function?
The code c operator d
is parsed as c(operator(d))
by Code.eval_string
, just like it would in normal Elixir, which means both c
and operator
must be functions of arity 1 for that expression to make sense. You wouldn't expect the following code to work, would you?
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?
Since these operators are just functions defined in Kernel
module, you can use apply/3
to call it:
iex(1)> c = 1
1
iex(2)> operator = :+
:+
iex(3)> d = 2
2
iex(4)> apply(Kernel, operator, [c, d])
3
So in your original code, just replace c operator d
with apply(Kernel, operator, [c, d])
.
There's also no need of eval_string
here.
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
Upvotes: 3