0.1 Eval

Code is data, data is code

It is important for one to think of code as data to understand what eval is all about. The idea is that the code itself can be manifested as a primitive data type which the language can then make sense of.

Example Code:

Output Window

The data here is the string that is being passed through, the code being the Ruby expression that evaluates to 42.

This is similar to doing a normal multiplication operation.

Example Code:

Output Window

One isn't restricted to just Ruby expressions, you can write absolutely any Ruby code using eval. In the example below, we re-define the method zen to return 42 instead of 41.
Example Code:

Output Window

Now, write any code in eval that returns a value that is greater than that stored in answer. Make sure that the expression below becomes true.

Hint

Try incrementing the answer.

Output Window

Ruby’s eval takes in a string as the argument, which means that it is possible for one to accept user input or read code off of a file and get it evaluated from within your program - essentially re-programming the way your program behaves.
Example Code:

Output Window

This of course means that any changes you need to make with the code you've read from the file have to be done by the use of Ruby's string manipulation methods.

This is unlike many functional programming languages where code itself is a first-class data type (known as homoiconicity) and you can eval code by just passing in code.

Output Window

Evaluating code read out of a file or a string coming from a database greatly increases the risk of execution of malicious code and is one of the reasons why eval is considered evil. eval also reduces the effectiveness of static code analysis tools, because our code is just masquerading as code, and is essentially just data.
Example Code:

Output Window

Thankfully, our system is pretty bullet-proof and this attempt at trying to rm -rf our root directory fails. eval should best be avoided in real scenarios. Ruby has saner tools (#define_method, #send) in its meta-programming repertoire that you can use to achieve eval-like cleverness.

Bindings

Now that we’ve gotten the idea through, we can look at how eval’s syntax works.

eval is a method on the Kernel module, which is included in the Object class, hence, available on all Ruby objects. eval takes in a second parameter along with the string where you can specify a binding.

A binding is an object that stores the context or the scope which it lies in. This allows us more granularity on where the code that we’ve passed is being eval’d in the context of the program.

Example Code:

Output Window

Here we see how self has changed in different binding contexts. This is how you can change self while evaluating code through eval using bindings.

Ruby provides with a constant TOPLEVEL_BINDING, which is a Binding object that always represents the top-level scope of your program.

Here's an example that uses TOPLEVEL_BINDING with eval that creates methods in the main scope from inside a class.

Example Code:

Output Window

It's a good practice to include the keywords __FILE__ and __LINE__ as the 3rd and 4th parameters to eval(). __FILE__ returns a string of the name of the file currently being executed. It returns "(eval)" if it's called from an eval() context.

Adding these parameters makes debugging code that uses eval much easier, so I would recommend that you use them every single time you use eval.

Note that we haven't necessarily used them in every example in this chapter to keep things simple.

Example Code:

Output Window

To summarise: Bindings are regular objects that contain scope and the state within it, but no code.

But what about #define_method?

In this section, we'll benchmark the performance of methods created by define_method and those created through eval.
Example Code:

Output Window

As we can see from the results, the methods defined using eval() performed better than those created using define_method.

I've re-worded this section based on feedback from one of our students, Alfonso Muñoz-Pomer Fuentes, who pointed out that eval itself performs poorly though methods created with it are quick. This is worth noting, because if you need to create a lot of new methods at runtime (a very rare scenario), define_method is the better option. Here's an example contributed by him that demonstrates the relative performance of creating lots of new methods using eval versus define_method.

Example Code:

Output Window

In general, I would caution against using eval despite any performance advantages because code that depends on eval tends to be harder to maintain for a variety of reasons that are beyond the scope of this course.

Congratulations, guest!


% of the book completed

or

This lesson is Copyright © 2011-2024 by Sidu Ponnappa and Jasim A Basheer