articleEiffel for Rubyists

francesco.ferreri's picture

After several months programming with Ruby scripting language, and related libraries, I got quite used to the intensive use of iterators and blocks (closures) that give Ruby programs a particular coding flavour. Switching back to Eiffel, I wondered how to reproduce this coding style in Eiffel programs: apart from discussing about this being an healthy practice or not, let's see some funny examples !

Simple iterators

One of the most frequently used iterators in Ruby is the each method in container classes like Array:

languages = Array.new(['Eiffel', 'Ruby', 'Python', 'C++', 'Perl', 'Java'])

languages.each do |lang|

 puts "I like " + lang 

end

this fragment of code basically gives:

I like Eiffel
I like Ruby
I like Python
I like C++
I like Perl
I like Java

The same result can be achieved in Eiffel by means of the do_all iterator of ARRAY class; the basic step is to substitute Ruby blocks (the portion do |varbind| ... end) with Eiffel inline agents:

feature 
   languages: ARRAY[STRING] is
			--
		once
			Result := <<"Eiffel", "Ruby", "Python", "C++", "Perl", "Java" >>
		end
 
    test is
        --
      do
         languages.do_all (agent (lang: STRING) do
             print("I like " + lang + "%N")  
         end) 
      end

Integer class iterators

Other common Ruby iterators can be found in the Integer class:

4.times do |t| puts "Hello, world!" end 1.upto(5) do |n| puts "going up: #{n}" end 10.downto(5) do |n| puts "going down: #{n}" end

The output of these three instructions is the following:

Hello, world!
Hello, world!
Hello, world!
Hello, world!
going up: 1
going up: 2
going up: 3
going up: 4
going up: 5
going down: 10
going down: 9
going down: 8
going down: 7
going down: 6
going down: 5

In Eiffel, INTEGER class is not equipped with such iterators, one could think of an INTEGER_ITERATOR class that implements them or, alternatively, write the following features that make the first integer argument explicit:

feature
	times (n: INTEGER; action: PROCEDURE[ANY,TUPLE]) is
			-- repeat `action' for `n' times
		local
			l_times: INTEGER
		do
			from
				l_times := 0
			until
				l_times = n
			loop
				action.call ([l_times])
				l_times := l_times + 1
			end
		end
 
	upto (min,max: INTEGER; action: PROCEDURE[ANY,TUPLE]) is
			--
		require
			min_lt_max: min <= max
		local
			l_index: INTEGER
		do
			from
				l_index := min
			until
				l_index > max
			loop
				action.call([l_index])
				l_index := l_index + 1
			end
		end
 
	downto (max,min: INTEGER; action: PROCEDURE[ANY,TUPLE]) is
			--
		require
			min_lt_max: min <= max
		local
			l_index: INTEGER
		do
			from
				l_index := max
			until
				l_index < min
			loop
				action.call([l_index])
				l_index := l_index - 1
			end
		end

Keeping in mind the Ruby example, these features should be called as follows:

times(4, agent (num: INTEGER) do 
  print("time: " + num.out + "%N") 
end)
 
upto(1,5, agent (num: INTEGER) do 
  print("going up: " + num.out + "%N") 
end)
 
downto(10,5, agent (num: INTEGER) do 
  print("going down: " + num.out + "%N") 
end)

Reading a file the Ruby way

And now a more advanced example, in Ruby a text file can be read as follows:

File.open("myfile.rb", "r") do |infile|

 while (line = infile.gets)
    puts line
 end

end

The open method in class File takes as argument a block operating on the file itself, it also takes care of closing the file after block execution, indeed a neat and compact style (but maybe not so explicit: should it be called open_do_something_and_close ?).

So, let's mimic this behaviour in Eiffel:

class
	RUBY_FILE
 
feature
 
	open (name: STRING; action: PROCEDURE[ANY,TUPLE]) is
			--
		local
			l_file: PLAIN_TEXT_FILE
		do
			create l_file.make_open_read (name)
			action.call ([l_file])
			l_file.close
		end
end

class RUBY_FILE exports the open feature as seen for the File class in Ruby, an usage example is the following:

feature
	test_ruby_file is
			--
		local
			l_ruby_file: RUBY_FILE
		do
			create l_ruby_file
			l_ruby_file.open ("myfile.rb", agent (f: PLAIN_TEXT_FILE) do
				from
					f.start
				until
					f.after
				loop
					f.readline
					print (f.last_string + "%N")
				end
			end)
		end

have fun with Eiffel and Ruby !

Comments

integer intervals and file line processing

upto can be done in Eiffel with integer intervals:

(1 |..| 5).do_all (agent ...)

it doesn't currently allow descending but we're not too far...

For reading files I'd rather see an iterator like API rather, where the processing agent takes a line, than having to iterate lines by hand: solves 2 problems in one go.

closure...

dfurrer's picture

Although generally somewhat more verbose, some patterns known from more functional languages can sometimes lead to very elegant solutions in Eiffel as well.

What I found a bit disappointing is that an inline agent can not capture local variables of the function it is being defined in though. (or am I missing something? Is this intended behaviour?)

create_function: FUNCTION[ANY, TUPLE[INTEGER], INTEGER]
    local
        x: INTEGER
    do
        .... compute some value for x ...
 
        Result := agent (a: INTEGER) do Result := a + x end
    end

You can by simply provide

manus_eiffel's picture

You can by simply provide the value. This was a conscious decision from the ECMA specification no to allow reuse of the parent locals because the semantic could become complex (e.g. what happens if the parent scope is gone?). The solution is to write your code as:

create_function: FUNCTION [ANY, TUPLE [INTEGER], INTEGER]
    local
        x: INTEGER
    do
        .... compute some value for x ...
 
        Result := agent (local_x, a: INTEGER) do Result := a + local_x end (x, ?)
    end

Same as call agents

colin-adams's picture

You aren't missing anything.

Inline agents are functionally identical to call agents, but they ensure that your code is less readable, and less maintainable.

Colin Adams

Reference Guide

Hi, Where can I See the full reference guide for eiffel? Thanks!

Most of the EiffelStudio

manus_eiffel's picture

Most of the EiffelStudio documentation is at http://docs.eiffel.com and it includes a language description. Or you can go to the ECMA website and fetch the PDF of the Eiffel standard.

The only reference guide

The only reference guide found on this page was this one: http://docs.eiffel.com/book/method/quick-reference-eiffel-language Which contains nothing.

Is there any online full docs, like the Java Docs? I'm trying to learn eiffel, and this wold be very helpful

Thanks!

Syndicate content