5

I have a database of "formulas" stored as strings. Let's assume for simplicity, that each formula contains 2 variables denoted by a and b, and that the formulas are all wellformed and it is ensured that it consists only of characters from the set ()ab+-*.

At runtime, formulas are fetched from this database, and from another source, numeric values for a and b are fetched, and the formulas are evaluated. The evaluation can be programmed like this:

# This is how it works right now
formula = fetch_formula(....)
a = fetch_left_arg(....)
b = fetch_right_arg(....)
result = eval(formula)

This design works, but I'm not entirely happy with it. It requires that my program names the free variables exactly the same as they are named in the formula, which is ugly.

If my "formula" would not be a string, but a Proc object or Lambda which accepts two parameters, I could do something like

# No explicitly named variables
result = fetch_proc(...).call(fetch_left_arg(....),fetch_right_arg(....))

but unfortunately, the formulas have to be strings.

I tried to experiment in the following way: What if the method, which fetches the formula from the database, would wrap the string into something, which behaves like a block, and where I could pass parameters to it?

# This does not work of course, but maybe you get the idea:
block_string = "|a,b| #{fetch_formula(....)}"

Of course I can't eval such a block_string, but is there something similar which I could use? I know that instance_eval can pass parameters, but what object should I apply it to? So this is perhaps not an option either....

5
  • I think the dentaku gem might be a very good fit here. You could use that gem or dive into its internals to figure out how it is done. Commented Feb 8, 2016 at 16:27
  • Note that even in "|a,b| #{fetch_formula(....)}", you have to know the variable names in advance. Commented Feb 8, 2016 at 16:33
  • It is not clear. Do you want or not want the formulae to be strings? Commented Feb 8, 2016 at 16:38
  • @sawa: The formulae have to be strings, because they are stored in the database. Commented Feb 9, 2016 at 6:48
  • @undur_gongor: Technically, you are right, but the "adding" of the parameter list would be done in the module which organizes to formulae, so it would logically fit there better. I could even store the whole string as part of the respective formula in the database. Commented Feb 9, 2016 at 6:51

4 Answers 4

3

This is very nasty approach, but for simple formulas you’ve mentioned it should work:

▶ formula = 'a + b'
▶ vars = formula.scan(/[a-z]+/).uniq.join(',')  # getting vars names
#⇒ "a,b"
▶ pr = eval("proc { |#{vars}| #{formula} }") # preparing proc
▶ pr.call 3, 5
#⇒ 8

Here we rely on the fact, that parameters are passed to the proc in the same order, as they appear in the formula.

Sign up to request clarification or add additional context in comments.

Comments

3

If I get your question correctly, it is something that I have done recently, and is fairly easy. Given a string:

s = "{|x, y| x + y}"

You can create a proc by doing:

eval("Proc.new#{s}")

2 Comments

I am not sure if this is a duplicate of mudasobwa's answer. I hope not.
It is not, because a) Proc.new#{s} is cleaner and b) nested eval is ugly in my answer.
2

One way to avoid creating the variables in the local scope could be to use a Binding:

bind = binding
formula = fetch_formula(....)
bind.local_variable_set :a, fetch_left_arg(....)
bind.local_variable_set :b, fetch_right_arg(....)
result = bind.eval(formula)

The variables a and b now only exist in the binding, and do not pollute the rest of your code.

1 Comment

I don't know how long I stared on the Ruby docs for the Binding class, because I thought a solution might be lurking there, but I couldn't find out how to do it. Thanks, this looks great!
2

You can create a lambda from string, as shown below:

formula = "a + b"
lambda_template = "->(a,b) { %s }"

formula_lambda = eval(lambda_template % formula)
p formula_lambda.call(1,2)
#=> 3

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.