Showing posts with label eval. Show all posts
Showing posts with label eval. Show all posts

Wednesday, March 02, 2011

Clojure: Eval-ing a String in Clojure

I recently needed to eval a string into Clojure and found it was easy, but it wasn't accomplished in the way I expected. My recent experience with eval was in Ruby, so I naturally reached for Clojure's eval function. I quickly found that Clojure's eval was not what I was looking for, well, not exactly what I was looking for.
(eval "(+ 1 1)")
"(+ 1 1)"
This quick trip to the REPL reminded me of the last time I needed to eval a string, and the previous experiences I'd had with read-string and load-string. It turns out load-string does the trick, and read-string + eval can also get the job done.
user=> (load-string "(+ 1 1)")
2
user=> (eval (read-string "(+ 1 1)"))
2
Looking at the documentation for read-string and load-string can help point you at which you might need for your given situation.
read-string: Reads one object from the string [argument]
load-string: Sequentially read[s] and evaluate[s] the set of forms contained in the string [argument]
Okay, it looks like you'd probably want load-string if you had multiple forms. Back to the REPL.
user=> (load-string "(println 1) (println 2)")                
1
2
user=> (eval (read-string "(println 1) (println 2)"))
1
As you can see, load-string evaluated the entire string and read-string only returned the first form to eval. The last examples show printing values (and I removed the nil return values), but it's more likely that you'll be concerned with returning something from read-string or load-string. The following example shows what's returned by both functions.
user=> (load-string "1 2")                           
2
user=> (eval (read-string "1 2"))
1
Given our previous results, I'd say load-string and read-string are returning the values you'd expect. It seems like it's easy to default to load-string, but that's not necessarily the case if evaluation time is important to you. Here's a quick snippet to get the point across.
user=> (time (dotimes [_ 100] (eval (read-string "1 2"))))
"Elapsed time: 12.277 msecs"
nil
user=> (time (dotimes [_ 100] (load-string "1 2")))
"Elapsed time: 33.024 msecs"
nil
One of the applications I previously worked on would write a large number of events to a log each day; the format used to log an event was a Clojure map. If the application was restarted the log would be read and each event would be replayed to get the internal state back to current. We originally started by using load-string; however, start-up time was becoming a problem, and a switch to read-string completely removed the issue.

Like so many examples in programming, there's a reason that two versions exist. It's worth taking the time to see which solution best fits your problem.

Monday, March 17, 2008

Move eval from Run-time to Parse-time

You need to use eval, but want to limit the number of times eval is necessary.

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
define_method attribute do
eval "@#{attribute} ||= #{default_value}"
end
end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

becomes

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
eval "def #{attribute}
@#{attribute} ||= #{default_value}
end"

end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

Motivation
premature optimization is the root of all evil -- Knuth, Donald
I'll never advocate for premature optimization, but this refactoring can be helpful when you determine that eval is a source of performance pain. The Kernel#eval method can be the right solution in some cases; but it is almost always more expensive (in terms of performance) than it's alternatives. In the cases where eval is necessary, it's often better to move an eval call from run-time to parse-time.

Mechanics
  • Expand the scope of the string being eval'd.
  • Test
Example

The following Person class uses eval to define the logic the readers rely upon for returning a default value if no value has previously been set.

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
define_method attribute do
eval "@#{attribute} ||= #{default_value}"
end
end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

The above example executes without issue, but it relies upon eval each time a reader is called. If multiple calls to eval are determined to be problematic the solution is to expand the eval to include defining the method itself.

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
eval "def #{attribute}
@#{attribute} ||= #{default_value}
end"

end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

Thursday, February 15, 2007

Ruby Evaluation options article on InfoQ

Yesterday, I published a fairly long article on InfoQ about Evaluation Options in Ruby. The article covers eval, instance_eval, class_eval and provides examples of using each of them.

Feedback welcome.