# available in standard Python

from functools import partial

# partial application

plus2 = partial(lambda x, y: x + y, 2)
print plus2(3)

# closures

def makeAdder(i):
    return (lambda n: i + n) # the lambda function is the closure
# end def makeAdder

fs = [makeAdder(i) for i in range(10)]
print [f(4) for f in fs]

def f():
    d = {'y' : 0}
    def g(): # this is the closure
        d['y'] += 1
        return d['y'] # "x" is a (used) free variable, therefore it is captured in the closure
    # end def g
    return g
# end def f

g = f()
print g.__closure__[0].cell_contents
print g()
print g.__closure__[0].cell_contents
print g()

# in Kachayev's fn package

from fn import _
from fn.op import zipwith
from itertools import repeat

# functions (be careful: in the interactive shell "_" means "last output"!)

print list(map(_ * 2, range(5)))
print list(filter(_ < 10, [9,10,11]))
print list(zipwith(_ + _)([0,1,2], repeat(10)))

print _ + _ * _

# streams

from fn import Stream

s = Stream() << [1,2,3,4,5]
print list(s)
print s[1]
print list(s[0:2])

s = Stream() << range(6) << [6,7]
print list(s)

def gen():
    yield 1
    yield 2
    yield 3
#end def gen

s = Stream() << gen << (4,5)
print list(s)

# lazy lists

from fn.iters import take, drop, map
from operator import add

f = Stream()
fib = f << [0, 1] << map(add, f, drop(1, f))

print list(take(10, fib))
print fib[20]
print list(fib[30:35])

# optimized tail recursion

from fn import recur

@recur.tco
def even(x):
    if x == 0: return False, True # i.e False => exit, True => return value
    return odd, (x-1,)# if it was True => call the same function, otherwise call another function
# end def even

@recur.tco
def odd(x):
    if x == 0: return False, False
    return even, (x-1,)
# end def odd

# usually it would fail!
print even(100000)

# partial application

from fn import F, _
from operator import mul

# F(f, *args) is the partial application
# like "functools.partial" but returns "fn.F"

print F(add, 1)(10)

# F << F means function composition, therefore (F(f) << g)(x) == f(g(x))
f = F(add, 1) << F(mul, 100)
print list(map(f, [0, 1, 2]))
print list(map(F() << str << (_ ** 2) << (_ + 1), range(3)))

# pipes

from fn.iters import filter, range

func = F() >> (filter, _ < 6) >> sum
print func(range(10))

# folding

from fn import op

folder = op.foldr(_ * _, 1)
print op.foldl(_ + _)([1, 2, 3])
print folder([1, 2, 3])

from fn import iters

print list(iters.accumulate([1,2,3], add))
print list(iters.flatten([[1,2], [3,4]]))
print list(iters.take(10, fib))
#print list(iters.consume(iter(iters.range(10))), 5) ???
pn = iters.padnone([10,11])
print list(iters.take(10, pn))

nc = iters.ncycles([10,11], 3)
print list(iters.take(10, nc))

rf = iters.repeatfunc(lambda: "test", 3)
print list(iters.take(3, rf))

print list(iters.grouper(3, "ABCDEFG"))

print list(iters.roundrobin('ABC', 'D', 'EF'))

(a, b) = iters.partition(lambda x: x % 2 == 1, iters.range(5))
print list(a), list(b)

(a, b) = iters.splitat(2, iters.range(5))
print list(a), list(b)

(a, b) = iters.splitby(lambda x: x % 2 == 0, iters.range(5))
print list(a), list(b)

print list(iters.powerset([1, 2]))
print list(iters.pairwise(range(4)))

# and other similar nice stuff (see https://pypi.python.org/pypi/fn)