So I found an interesting paper describing how to use lazy lists to represent infinite series and i thought I’d try to do the same thing in python using generators, which are similar.

It’s cool but kind of a mess. I assume with more thought this could be tweaked into a clear thing. The thunking is especially gross. Maybe totally unnecessary? At some point I felt like it was the right thing to do for some reason and then I stuck with it.

The obvious simpler way would be to truncate the series at n=100 or something and just use lists. Why would I ever want terms higher than that anyhow? Interesting exercise though.

# lazy evaluation
# sometimes a problem can be simple if we're working in an insanely huge space.
# if you can proceed thorugh the space lazily, this might be an acceptable strategy


# decorator to convert function into a function that returns a thunk that will evelauted to the original function
def thunkify(func):
	def func_wrapper(*args, **kwargs):
		return lambda: func(*args, **kwargs)
	return func_wrapper

#geometric series
def geo():
	while True:
		yield 1

#exponential expansion
def exp():
	num = 0.
	fact = 1.
	yield 1.
	while True:
		num += 1.
		fact = fact * num
		yield 1./fact

@thunkify
def const(c):
	yield c
	while True:
		yield 0

def oneX():
	yield 0
	yield 1
	while True:
		yield 0

@thunkify
def convertArray(myarray):
	for i in myarray:
		yield i
	while True:
		yield 0

# oneX = convertArray([0,1])


myexp = exp()
print next(myexp)
print next(myexp)
print next(myexp)

''' #an old unthunked approach. I need to instantiate all generators before passing them.
def add(a, b):
	while True:
		yield next(a) + next(b)

mysum = add(oneX(), const(3))
print next(mysum)
print next(mysum)
print next(mysum)
'''


#If i do this way, need to always returns thunks for all constructors.
# such as const(3): def thunk
@thunkify
def add(a, b):
	#could this be lazier? # do I want egnerator combinators to instantiate the genereators?
	a = a()
	b = b()
	while True:
		yield next(a) + next(b)


mysum = add(oneX, const(3))
mysum = mysum()
print next(mysum)
print next(mysum)
print next(mysum)


#I fear rethunking will lead to weird bugs. rehtunked guys might be linked to each other in ways you might not expect
def rethunk(a):
	return lambda: a

@thunkify
def constmult(c, a):
	a = a()
	while True:
		yield c * next(a)



@thunkify
def mult(a,b):
	abar = a()
	bbar =b()
	a0 = next(abar)
	b0 = next(bbar)
	bbar = rethunk(bbar)
	abar = rethunk(abar)
	yield a0 * b0
	remainprod = add(constmult(a0, bbar), mult(abar,b))
	remainprod = remainprod()
	while True:
		yield next(remainprod)

@thunkify
def integrate(C, a):
	num = 0.
	a = a()
	yield C
	while True:
		num += 1.
		yield next(a)/num

@thunkify
def differentiate(a):
	a = a()
	num = 0.
	next(a) #toss away constant
	while True:
		num += 1.
		yield num * next(a) 


def evalseries(x, n, series):
	pass

def take(n, gen):
	gen = gen()
	arr = []
	for i in range(n):
		arr.append(next(gen))
	return arr

print take(5, exp)
print take(5, mult(oneX, exp))
print take(5, integrate(0,oneX))
print take(5, differentiate(oneX))
print take(5, differentiate(exp))