Showing posts with label strings. Show all posts
Showing posts with label strings. 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.

Thursday, December 20, 2007

Avoiding costly typos

Typos are generally unfortunate, but greatly upsetting when they cost you a few hours of your life. I have a few rules I try to follow in an attempt to conserve those valuable hours in the future.

Ruby has symbols. I love symbols. But, using symbols for comparison is an easy way for a typo to cost you time.

# example 1
name = :shane
name == :shane # => true
name == :chane # => false

# example 2
class Name
def self.shane
:shane
end
end

name = Name.shane
name == Name.shane # => true
name == Name.chane # => undefined method `chane' for Name:Class (NoMethodError)

Above, in example one, a typo simply returns false. However, in example two the typo gives me the immediate feedback that I made a mistake. You could argue that I should simply use a constant. Elephant case (All upper case) words bother me for some reason, but if you prefer constants that's cool too, you'll get the same benefit.

The method_missing method is dynamite. Used appropriately it's a powerful tool. However, there are often times when you simply don't need dynamite.

# example 3
class State < Struct.new(:state)
def method_missing(sym, *args)
sym.to_s.delete("?") == self.state
end
end

State.new("ready").ready? # => true
State.new("ready").reddy? # => false

# example 4
class State < Struct.new(:state)
def method_missing(sym, *args)
sym.to_s.delete("?") == self.stat
end
end

State.new("ready").ready? # ~> -:12:in `method_missing': stack level too deep (SystemStackError)

# example 5
class State < Struct.new(:state)
[:ready, :running, :finished].each do |element|
define_method :"#{element}?" do
self.state == element.to_s
end
end
end

State.new("ready").ready? # => true
State.new("ready").reddy? # ~> -:10: undefined method `reddy?' for #<struct State state="ready"> (NoMethodError)


Examples three and four illustrate the two common typos that can cost you time while utilizing method_missing. Example five shows an alternative that requires slightly more code, but is significantly better at letting you know when you've made a mistake.

If right now you are thinking "that's nice, but I write tests so I'll catch it there" then you get points for writing tests, but you missed one important note: If the typo is in your test you could be getting a false positive. I once found a bug where the same typo existed in a class and the test for the class. The result was broken production code and a green test suite.

The last tip builds on the first two: Don't use strings for comparison. As an alternative to using constants or class methods, you could define methods on a string (or a symbol) to query for the value.

RAILS_ENV = "development"
class << RAILS_ENV
["development", "test", "production"].each do |environment|
define_method :"#{environment}?" do
self == environment
end
end
end

RAILS_ENV.test? # => false
RAILS_ENV.development? # => true
RAILS_ENV.developmant? # ~> -:12: undefined method `developmant?' for "development":String (NoMethodError)

The last example is nice because it allows you to type less when doing a comparison and provides you better feedback if you do make a typo. (drop a +1 on this ticket if you want this feature in Rails core: http://dev.rubyonrails.org/ticket/10583)