functional style provides benefits. You should do it whenever it is convenient, and you should think hard about the decision when it isn’t convenient. “ ” John Carmack, ID Software 26th april 2012
functions & closures. Higher-order functions everywhere Lazy evaluation Functional way: a cook's tour Pure functions (referential transparency) sin(n), log2(n) Composable Execution order insignificant Data transformations e.g. XSLT etc...
in python are limited to a single expression. Types (haskell as a reference): Pattern matching does not exist in python. Unlike python, haskell has static type checking with type inference. sum :: (Num a) => [a] -> a
for y in xrange(6, 11) if y-x == 3] List comprehension in python [(x, y) | x <- [1..5], y <- [6..10], y-x == 3] is inspired by haskell's syntax: List comprehension has largely superceded the utility of the filter HOF.
while Execution of recursive functions typically uses a stack. e.g. naïve sum function. sum :: [Integer] -> Integer sum [] = 0 sum (x:xs) = x + sum xs # haskell
+ sum_(seq[1:]) # python naïve sum function This does not work if you pass in an iterator (e.g. generator expression) as the argument. We will fix this in upcoming slides.
A recursive function is tail recursive if the return value of the recursive call is the final result of the function. tail recursive conversion >>> sum_([i for i in xrange(1000)]) RuntimeError: maximum recursion depth exceeded fibonacci(n-1) * fibonacci(n-2) seq[0] + sum_(seq[1:]) Example of non tail-recursive calls. sum :: [Integer] -> Integer -> Integer sum [] acc = acc sum (x:xs) acc = sum xs (x + acc) # haskell
of active tail calls (recursive or otherwise). Haskell being a lazy language has a different approach. tail recursion elimination def sum_(seq): def tail_sum(seq, acc): if not seq: return acc else: return tail_sum(seq[1:], seq[0]+acc) return tail_sum(seq, 0) Python does not perform Tail Call Optimization (a.k.a Tail Recursion Elimination). The BDFL objects to this. O(n) => O(1)
the python stack. Trampoline technique Use core python features. e.g. __builtins__.sum A trampoline is a function executor. Works for tail-recursive functions (and also coroutines with tail-calls). Avoids stack overflow.
def tail_sum(seq, acc): it = iter(seq) try: first, rest = (next(it), list(it)) except StopIteration: return acc else: return thunk(tail_sum)(rest, first + acc) Delayed computation Trampoline code is based on example by James Tauber @jtauber
the code that extracts the head & tail of the iterator. def tail_sum(seq, acc): it = iter(seq) try: first, *rest = it except ValueError: return acc else: return thunk(tail_sum)(rest, first + acc) #python 3
yet? bouncer = bouncer() return bouncer def sum_(seq): return trampoline(thunk(tail_sum)(seq, 0)) Trampoline code is based on example by @jtauber bounce land >>> sum_(i for i in range(2000)) 1999000
i for i in xrange(10000) ) 49995000 Comparisons of benchmarks using timeit : >>> sum_( i for i in xrange(10000) ) 49995000 1.66 secs >>> sum_( i for i in xrange(100000)) 4999950000L 150 secs 7.79 msecs >>> sum( i for i in xrange(100000)) 4999950000L
(sum xs) # haskell It encapsulates the recursive pattern of processing data structures (lists for simplicity). [2] Graham Hutton – A tutorial on the universality & expressiveness of fold, 1999 Universal property [2] of the fold operator: g [] = z g (x:xs) = f x (g xs) fold f z Thus, sum simplifies to: Prelude> foldl (+) 0 [1..3] 6 arity 2 finite lists It reduces a list to a single value.
from the left (the start) and foldr for folding from the right (the end). Prelude> let f = (\acc x -> 1 + acc) Prelude> foldl f 0 [0..3] 4 Example: finding the length 0 0 f f 1 f 2 f 3 left fold
version of the fold operator. Left & right folds are handled by same function. def reverse(seq): def r(acc, x): acc.insert(0, x) return acc return reduce(r, seq, []) Example: reverse a list Let's implement haskell's elem using right fold. Prelude> elem 2 [1..3] True Prelude> elem 0 [1..3] False
x == item: return True else: return acc return reduce(lambda a, b: match(b, a), _seq, False) Example: haskell's elem using right fold. The initial value is set to False. The accumulator remains False until a match is found.
of benchmarks using timeit : >>> reduce(operator.add, i for i in xrange(10000), 0) 1.38 msecs >>> reduce(operator.add, i for i in xrange(100000), 0) 13.6 msecs 7.79 msecs >>> sum(i for i in xrange(100000)) Simple benchmark of reduce >>> import operator
properties of reduce reduce( lambda a, b: a | b , [True, False, False], False ) # any reduce does not short-circuit; which would explain why it is slower than the builtin any or all for use cases below: reduce( lambda a, b: a & b , [False, False, True], False ) # all Homework: Implement map & filter using reduce.
return lambda a, b: f(a)(b) >>> original_sub = uncurry2(curried_sub) >>> assert original_sub(1, 2) == -1 All functions are curried in Haskell. [1] subtract :: (Num a) => a -> a -> a as oppose to the uncurried form: subtract :: (Num a) => (a, a) -> a
? Theoretical – able to treat functions uniformly. Practical – aids in the creation of partially applied functions. Bind some values to some (but not all) of the arguments of a curried function. Partial application # haskell ghci> map (2*) [0, 1, 2] [0, 2, 4] ghci> map (subtract 1) [0, 1, 2] [-1, 0, 1] PEP 309 standardizes a partial object (since python 2.5).
a - b.' >>> subtract = partial(operator.sub, 1) >>> subtract(2) # evaluating 1 - 2 -1 >>> subtract.keywords {} >>> subtract.args (1,) Caveat ! Since operator is a C module, you cannot bind values to specific positional arguments.
slide does not apply to user defined functions. Thus, >>> def minus(a, b): 'clone of operator.sub' return a - b >>> subtract2 = partial(minus, b=2) >>> subtract2.keywords {'b': 2} >>> subtract2.__doc__ 'partial(func, *args, **keywords) ...' >>> from functools import update_wrapper >>> update_wrapper(subtract2, minus) >>> subtract2.__doc__ 'clone of operator.sub'
instance methods. partial can be used to create thunks (as opposed to hand-crafted lambdas) It is ok to imagine operator.itemgetter & operator.attrgetter as curried functions (although their implementation does not match that mental model).
A parser is built up from smaller primitive parsers. Parser combinators are just higher-order functions. Parser :: String AST → Combinator :: Parser Parser Parser → → Thus, grammar construction for things like repetition , sequencing or choice is modelled using combinators. Example: trivial marathon running time parser using funcparselib. parse(“02:15:25”)==(2,15,25)