2017-10-09 1 views
0

J'essaye d'écrire une fonction de module simple qui prend une expression comme une chaîne comme "123 + 45" et renvoie une réponse. Alors:Elixir: opérateur d'infixe dynamique

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

Je pensais à diviser simplement la chaîne sur (" ") pour obtenir les entiers et l'opérateur, puis en appelant essentiellement Code.eval_string sur elle. Il y avait là ma première tentative naïve:

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 

Puis, en exécutant, je reçois cette erreur:

** (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 

Je ne pouvais pas comprendre pourquoi c était en cours d'évaluation en fonction. Je soupçonne que cela a à voir avec la variable operator dans la fonction anonyme. J'ai confirmé qu'en le réécrivant avec un opérateur codé en dur:

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 

En effet, ceci produit le résultat attendu. La question est:

Pourquoi la présence de la liaison de variable operator dans la fonction anonyme a-t-elle entraîné l'évaluation de c en tant que fonction?

Il ressort de la documentation sur Code.eval_string fait apparaître comme si les fixations variables peuvent être à peu près tout aussi longtemps qu'ils sont trouvés dans la liste de mots clés est le deuxième argument eval_string. Dans ma deuxième tentative, j'ai pensé à essayer de convertir l'opérateur de la chaîne d'entrée dans un atome et de convertir l'opérateur d'un infixe à un appel de fonction (par exemple, de 1 + 3 à quelque chose comme 1.(:+, [3]).) Mais cela ne fonctionne pas. . semblent être une syntaxe valide

ma deuxième question est:

est-il possible d'écrire une expression avec un opérateur infixe tel que + en fonction, et de pouvoir ne définir dynamiquement opérateur un atome?

Répondre

2

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

Le code c operator d est analysé comme c(operator(d)) par Code.eval_string, tout comme il le ferait dans Elixir normal, ce qui signifie à la fois c et operator doivent être des fonctions d'arité 1 pour cette expression de sens. Vous ne vous attendriez pas à ce que le code suivant fonctionne, n'est-ce pas?

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?

Étant donné que ces opérateurs ne sont que des fonctions définies dans Kernel module, vous pouvez utiliser apply/3 pour l'appeler:

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

Donc, dans votre code d'origine, il suffit de remplacer c operator d avec apply(Kernel, operator, [c, d]).

Ici aussi, il n'y a pas besoin de 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