« Earlier2 items total Later »

Ruby style Python

After upsetting the Gods by getting Ruby to look a a bit like python, I thought about trying to offend in the opposite direction.

The most distinguishing thing about Ruby of course is the block notation which everybody, except pythonistas, love. They have lambda but it is just allows a single expression. Anonymous blocks have been deliberately left out of the language but with some hackery it is possible to get close to what we want.

The solution still has some cruft but it is pure python and just uses that standard toolkit of for loops, generators, iterators and decorators.

The trick is to decorate an iterator so that it has a few more desired features. Ie we need to be able to return values from the body of the for loop back into the generator.

The decorator adds an extra ret function parameter to the head of the parameter list of the generator function. This enables the generator to provide a final value to the top level code.

It also adds another r parameter to the head of the parameter list in the body of the for loop so that it can return values to the generator after every iteration.


from ruby import rubyfi

@rubyfi
def inject(ret, iter, a = 0):
   for x in iter:
      a = yield a, x
   ret(a)

@rubyfi
def collect(ret, iter):
   r = []
   for x in iter:
      x = yield x,
      r.append(x)
   ret(r)

# Do a sum over a range
for r, s, x in inject ( range (1,6), 1 ) :
   r(s+x)
print r()

# Generate a list
for r, s, x in inject ( range (1,6), [] ) :
   s.append(x*x)
   r(s)
print r()

# Collect items
for r, x in collect ( range (1,6) ) :
   r(x+x)
print r()



which generates
>> python iterator.py
16
[1, 4, 9, 16, 25]
[2, 4, 6, 8, 10]


The library to make it work (ruby.py) is


class Interceptor:

   def value(self, *v):
      if len(v) > 0:
         self.val = v[0]
      return self.val

   def __init__(self, generator):
      self.generator = generator
      self.val = None

   def __iter__(self):
      return self

   def next(self):

      def c(*args, **kwargs):
         return self.value(*args, **kwargs)

      y = self.generator.send(self.val)

      r = [c]
      r.extend(y)
      return r


def rubyfi(function):

      def wrapper(*args, **kw):

         g = []

         def ret(val):
            g[0].value(val)

         generator = function(ret, *args, **kw)

         g.append ( Interceptor(generator))

         return g[0]

      return wrapper



The critical trick I discovered was switching the next method which is called by default by the for loop mechanism into a call to the send method of the original generator. This allows me to feed values back into the iteration from the for loopp.

It is still uglier than Ruby but it can be done

:)

Python style Ruby.

Following from a thread on comp.lang.ruby I wondered what it would take to make Ruby more like Python. Yuk you say! Well probably but here is a start.

I updated the code after reading Giles Bowkett's" response to my post about needing the *.pyrb files. After a little poking around and finding hyphen-ruby I discovered a smarter way to do this.

I also made some other improvements to allow deeper dedenting. The trick to the new version is to use the __END__ keyword to block the Ruby scanner and then reload the current file after doing the preprocessing.

Also you only need the __END__ thingie in the first file that is loaded. All subsequent loads will do the preprocessing automatically.



test.pyrb

require 'pyrb.rb'
__END__

def foo:
    [1,2,3,4].each do |i|:
        puts i
        [1,2,3,4].each do |j|:
            puts i
        if i == 2 :
            puts "foo"
        else:
            puts "bar"


foo




pyruby.rb

module PyRuby
    public


    def self.require(base_name)
        if $".include? base_name
            return false
        else
            file = find_file base_name
            if file
                load(file)
                $" << base_name
                return true
            end
        end
    end

    def self.load(file)
        if file
            txt = PyRuby.pyrb_convert file
            begin
                Object.module_eval txt, file, 1
            rescue SyntaxError => e
                $stderr.print "Preprocessing Fail: " + $!
                $stderr.print "-----------------------------------\n"
                $stderr.print txt
                raise
            end
            return true
        end
        false
    end

    def self.find_file(fname)
        if FileTest.exist?(fname)
            return fname
        else
            $:.each do |path|
                path = File.join(path, fname)
                for ext in [ '.rb' ] do
                    p = path + ext
                    return p if FileTest.exist?(p)
                end
            end
        end
    end

    def self.pyrb_convert file
        txt = ""
        stack = []
        found_pyrb_req = false
        do_preprocess = false

        File.open file do |fh|


            fh.each_line do |l|
                l.chomp!
                if l =~ /^require\s*'pyrb.rb'\s*$/
                    found_pyrb_req = true
                end
                if l =~ /^__END__\s*$/ && found_pyrb_req
                    do_preprocess = true
                    txt = ''
                elsif do_preprocess

                    # Do the indent preprocessing
                    indent = (l.gsub /\S.*/,'').length
                    stack.reverse.each do |pindent|
                        if indent <= pindent
                            case l.gsub /^\s*/,''
                            when /(else|end|rescue).*/
                            else
                                txt << (" " * pindent) + "end\n"
                            end
                            stack.pop
                        end
                    end
                    if l =~ /:\s*$/
                        stack.push indent
                        l.gsub! /:\s*$/,''
                    end
                    txt << l << "\n"


                else
                    # Treat the code as normal Ruby code
                    # and pass straight through.
                    txt << l << "\n"
                end
            end
        end
        if do_preprocess
            while not stack.empty?
                txt << (" " * stack.pop) + "end\n"
            end
        end
        txt
    end
end

alias __pyrb_load load

def load base_name
    PyRuby.load base_name
end

def require base_name
    PyRuby.require base_name
end

PyRuby.load $0



( updated 29/05/2007 )

« Earlier2 items total Later »




Sponsored by

Sole Central

Your one stop shop for Birkenstock and Crocs shoes and sandles.