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
:)