Recently I argued that Python's whitespace-aware structure made it inhospitable to functional programming. It occurred to me that I should try and demonstrate the problem by partially porting Microsoft's LINQ library to Python. After all, code speaks louder than words :-).
Here is the code:
class Enumerable:
def __init__(self, func):
self.func = func
def __select__(self, func):
for item in self.func():
yield func(item)
def __where__(self, func):
for item in self.func():
if func(item):
yield item
def __join__(self, stream):
for left in self.func():
for right in stream.func():
yield (left, right)
def __take__(self, count):
iterator = self.func()
for counter in xrange(count):
yield iterator.next()
def __skip__(self, count):
iterator = self.func()
for counter in xrange(count):
iterator.next()
while 1:
yield iterator.next()
def take(self, count):
return Enumerable( lambda: self.__take__( count) )
def skip(self, count):
return Enumerable( lambda: self.__skip__( count) )
def join(self,stream):
return Enumerable( lambda: self.__join__( stream) )
def __iter__(self):
return self.func()
def select(self, func):
return Enumerable( lambda: self.__select__( func) )
def where(self, func):
return Enumerable( lambda: self.__where__( func) )
def to_list(self):
list = []
for item in self.func():
list.append(item)
return list
def iterate(initialValue, func):
while 1:
yield initialValue
initialValue = func(initialValue)
def zip(enum1, enum2):
return Enumerable(lambda: __zip__(enum1,enum2))
def __zip__(enum1, enum2):
iter1 = enum1.__iter__()
iter2 = enum2.__iter__()
while 1:
yield (iter1.next(), iter2.next())
This library essentially adds lazy evaluation to stream operations in Python. This allows me to write the fibonnaci set in the same way that I would write it in Haskell:
def fibs():
yield 0
yield 1
fibsEnum = Enumerable(fibs)
for value in zip (fibsEnum, fibsEnum.skip(1)).select(lambda item: item[0] + item[1]):
yield value
fibNums = Enumerable(fibs)
fibNums = fibNums.take(10)
fibNums.to_list()
The code above prints:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
The example above is a terrible implementation of the fibonnacci set but is a good demonstration of laziness. This library accomplishes the following:
- Adds lazy evaluation to stream operations
- Creates wrapper object around iterator function, allowing new stream operations to be added using metaclasses which...
- Allows stream transformations to be written in the order they are executed with the first operation occuring on the left and the last on the right.
As you can see the limitation that lambda functions can only be one line long necessitates two function definitions for each stream transformation function. All these additional identifiers produce a lot of noise in the code that detracts from its readability. Thankfully that noise can be hidden in the class definition.
The ability to write stream transformations in the order of execution (left-to-right) greatly improves readability in my opinion. However the issue of having to put an entire expression on a single line is still an insoluble problem.
I would like nothing better than to hear that I am totally wrong, there is a much better and more Pythonic approach, that I just don't get it, or some such thing.
Any skilled Python programmers who have a better approach that addresses the readability issues I outline?