AN OVERVIEW OF
FUNCTIONAL
PROGRAMMING IN PYTHON
Edward D. Weinberger, Ph.D, F.R.M.
Adjunct Professor
NYU Tandon School of Engineering
edw@wpq-inc.com
ULTIMATE PURPOSE
To avoid
dysfunctional
programming!
OUTLINE
• Functional programming: definition and
advantages
• Functional programming: key concepts
• Python support for functional programming:
the basics and beyond
• Potential pitfalls and best practices
FUNCTIONAL
PROGRAMMING DEFINED
“A programming paradigm that
treats computation as the
evaluation of mathematical
functions, avoiding changes of
state and mutable data”
IMPLICATIONS OF
DEFINITION
• Output values of functions must depend
only on inputs, not on internal state
variables
• Calling a function twice with the same
inputs results in the same output
“IMPERATIVE
PROGRAMMING” NO-NO’s
• For/While Loops
• Internal “state” variables (e.g. variables
assigned in __init__() and updated by other
methods)
• Re-assignment (mutability) of variables
OBVIOUS QUESTION:
Why bother???
IMPERATIVE VERSION OF
QUICKSORT
def quicksort(array):
indexes_stack = list()
idx = (0, len(array) - 1)
indexes_stack.append(idx)
for idx in indexes_stack:
elem_idx = idx[0]
pivot_idx = idx[1]
while pivot_idx > elem_idx:
pivot = array[pivot_idx]
elem = array[elem_idx]
if elem > pivot:
array[pivot_idx] = elem
array[elem_idx] = array[pivot_idx - 1]
array[pivot_idx - 1] = pivot
pivot_idx -= 1
else:
elem_idx += 1
boundar_low = idx[0]
boundar_high = idx[1]
if pivot_idx > 1 and boundar_low < pivot_idx - 1:
indexes_stack.append((boundar_low, pivot_idx - 1))
if pivot_idx < len(array) -1 and pivot_idx + 1 < boundar_high:
indexes_stack.append((pivot_idx + 1, boundar_high))
return array
FUNCTIONAL VERSION OF
QUICKSORT
def quicksort(lst):
if len(lst) == 0:
return lst
pivots = list(filter(lambda x: x == lst[0], lst))
small = quicksort(list(filter(lambda x: x < lst[0], lst)))
large = quicksort(list(filter(lambda x: x > lst[0], lst)))
return small + pivots + large
OTHER ADVANTAGES
(besides ease of reading/debugging)
• May facilitate
• Compiler optimization
• Parallelization
• Faster execution via memorization
• Lazy evaluation
• Academic studies of formal proofs of
program correctness have made
progress under functional programming
assumptions
FUNCTIONAL
PROGRAMMING: A FEW
KEY CONCEPTS
PURE FUNCTIONS
• Functions that
• always return the same result, given the same
argument, so sin(x) is pure, today() is impure
• do not have “side effects”
• Cannot use internal state to determine
what result to return
• Python functions may or may not be pure
REFERENTIAL
TRANSPARENCY
• Describes a function/expression that can be
replaced by its value without changing the
behavior of a program in which it is
embedded (e.g. sin(x)+5)
• Generally, an expression can only be
referentially transparent if any functions
used are pure
“HIGHER ORDER”
FUNCTIONS
• Functions that take other functions as
inputs and/or outputs
• Python examples:
• Python’s sorted function takes a key parameter,
the name of a function that returns a sort key
• A GUI button object’s __init__() might be
passed the name of the function to be executed
when the button is clicked
PYTHON SUPPORT FOR
FUNCTIONAL
PROGRAMMING
SUPPORT FOR FP IN
“EVERY DAY” PYTHON
• Recursion
• Lambda expressions
• All functions are “first class functions”, i.e.
function names can be
• passed as arguments
• assigned to variables
• appear as elements of lists
• etc.
PYTHON ITERABLES
AND ITERATORS
• An iterable is an object that defines either a
__getitem__() or an __iter__() method
• The built-in function iter(iterable) returns
an iterator (also returned by generators)
• Iterators define a (private) __next__()
method, called by the built-in function
next(), raising the StopIteration error
when nothing is next
ITERABLE/ITERATOR
EXAMPLE
>>> aList = [1, 2] #need not be a list
>>> aListIter = iter(aList) #possibly
>>> next(aListIter)# lazy evaluation
1
>>> next(aListIter)#
2
>>> next(aListIter)
StopIteration Traceback …
Returns an iterator which is the result of
applying <function> (of one argument) to
successive individual items supplied by
<iterable>
EXAMPLE:
>>> list(map(lambda x: x*x,[1,3,5,7]))
[1, 9, 25, 49]
map(<function>, <iterable>)
MAP RETURNS AN
ITERATOR
>>> seq = map(lambda x: x*x, [1, 3, 5, 7])
>>> seq
<map at 0x…>
>>> next(seq)
1
>>> next(seq)
9
applies <function> (of two arguments) to the
first two items supplied by <iterable>, thus
“reducing” them to a single value. This result
is supplied, along with the third item from
<iterable> thus “reducing” all three items to
a single value
Note: reduce must be imported from the
built-in library functools
reduce(<function>, <iterable>)
WORKING WITH reduce
>>> from functools import reduce
>>> reduce(lambda x,y: x*y, range(1, 6))
120 #The product of ((1*2)*3)*4)*5
Constructs an iterator for those elements of
<iterable> for which <function> returns True
Equivalent to the generator expression
(for item in <iterable>
item if <function>(item)
)
filter(<function>, <iterable>)
WORKING WITH filter
>>> isEven = lambda x: (x%2 == 0)
>>> yetAnotherList = [12, 43, 33, 32, 31]
>>> it = filter(isEven, yetAnotherList)
>>> next(it)
12
>>> next(it)
32
>>> list(filter(isEven, yetAnotherList))
[12, 32]
AN EXTENDED EXAMPLE
min_order = 100
invoice_totals = list(
map(lambda x: x if x[1] >= min_order
else (x[0], x[1] + 10),
map(lambda x: (x[0],x[2] * x[3]), orders)))
WHAT ELSE IS THERE TO
KNOW?
• More jargon (see Wikipedia)
• The operator, functools and itertools
libraries
• Various ideas about pitfalls/best practices
PITFALLS/BEST
PRACTICES IN
FUNCTIONAL
PROGRAMMING
BIGGEST PITFALL:
“It’s the new kid on the block!
• After 60+ years,
• Imperative programming (IP) is well understood
• Lots of hardware and compiler support for IP
• IP code has large code base
RECURSION IS
PROBLEMATIC
• Recursion is always relatively slow and
memory intensive because of the many
subroutine calls usually required
• Python, in particular, only grudgingly
supports recursion
• Implementation not particularly fast
• Finite recursion depth, even for tail recursion
FP “Update as you copy”
PARADIGM PROBLEMATIC
• The alternative to updating mutable
variables is “update as you copy”
• “Update as you copy” leads to memory
bloat
• Can be difficult to keep track of what’s
going on if nested objects are being
copied/updated
FP “Update as you copy”
PARADIGM PROBLEMATIC
• The alternative to updating mutable
variables is “update as you copy”
• “Update as you copy” leads to memory
bloat
• Can be difficult to keep track of what’s
going on if nested objects are being
copied/updated
PURE FUNCTIONS AND I/O
DON’T REALLY MIX
• Python functions such as input()are not
even close to referentially transparent!
• Internal state variables needed for buffered
reads/writes
BEST PRACTICES SPECIFIC TO
FUNCTIONAL PROGRAMMING
• Use map, reduce, filter, and other library
functions in place of recursion if possible
• Cleanly separate pure functions from
intrinsically impure functions (such as I/O)
• Combine related functions in a class and
use inheritance (yes, FP and OO do mix!)
BEST PRACTICES APPLICABLE TO
ALL PYTHON PROGRAMMING
• “short and sweet” functions, doing one
thing well (1 or 2 ISO/ANSI 24x80 screens)
• Take advantage of Python’s polymorphism
• Don’t pack too much into a single line of
code
• Use descriptive variable names
BEST PRACTICES PER “THE ZEN
OF PYTHON”
• “… practicality beats purity”
• “Readability counts”
• “If an implementation is hard to explain, it’s
a bad idea”
• “If an implementation is easy to explain, it
might be a good idea”
RECAP
• Functional programming: definition and
advantages
• Functional programming: key concepts
• Python support for functional programming:
the basics and beyond
• Potential pitfalls and best practices

Functional programming in python

  • 1.
    AN OVERVIEW OF FUNCTIONAL PROGRAMMINGIN PYTHON Edward D. Weinberger, Ph.D, F.R.M. Adjunct Professor NYU Tandon School of Engineering edw@wpq-inc.com
  • 2.
  • 3.
    OUTLINE • Functional programming:definition and advantages • Functional programming: key concepts • Python support for functional programming: the basics and beyond • Potential pitfalls and best practices
  • 4.
    FUNCTIONAL PROGRAMMING DEFINED “A programmingparadigm that treats computation as the evaluation of mathematical functions, avoiding changes of state and mutable data”
  • 5.
    IMPLICATIONS OF DEFINITION • Outputvalues of functions must depend only on inputs, not on internal state variables • Calling a function twice with the same inputs results in the same output
  • 6.
    “IMPERATIVE PROGRAMMING” NO-NO’s • For/WhileLoops • Internal “state” variables (e.g. variables assigned in __init__() and updated by other methods) • Re-assignment (mutability) of variables
  • 7.
  • 8.
    IMPERATIVE VERSION OF QUICKSORT defquicksort(array): indexes_stack = list() idx = (0, len(array) - 1) indexes_stack.append(idx) for idx in indexes_stack: elem_idx = idx[0] pivot_idx = idx[1] while pivot_idx > elem_idx: pivot = array[pivot_idx] elem = array[elem_idx] if elem > pivot: array[pivot_idx] = elem array[elem_idx] = array[pivot_idx - 1] array[pivot_idx - 1] = pivot pivot_idx -= 1 else: elem_idx += 1 boundar_low = idx[0] boundar_high = idx[1] if pivot_idx > 1 and boundar_low < pivot_idx - 1: indexes_stack.append((boundar_low, pivot_idx - 1)) if pivot_idx < len(array) -1 and pivot_idx + 1 < boundar_high: indexes_stack.append((pivot_idx + 1, boundar_high)) return array
  • 9.
    FUNCTIONAL VERSION OF QUICKSORT defquicksort(lst): if len(lst) == 0: return lst pivots = list(filter(lambda x: x == lst[0], lst)) small = quicksort(list(filter(lambda x: x < lst[0], lst))) large = quicksort(list(filter(lambda x: x > lst[0], lst))) return small + pivots + large
  • 10.
    OTHER ADVANTAGES (besides easeof reading/debugging) • May facilitate • Compiler optimization • Parallelization • Faster execution via memorization • Lazy evaluation • Academic studies of formal proofs of program correctness have made progress under functional programming assumptions
  • 11.
  • 12.
    PURE FUNCTIONS • Functionsthat • always return the same result, given the same argument, so sin(x) is pure, today() is impure • do not have “side effects” • Cannot use internal state to determine what result to return • Python functions may or may not be pure
  • 13.
    REFERENTIAL TRANSPARENCY • Describes afunction/expression that can be replaced by its value without changing the behavior of a program in which it is embedded (e.g. sin(x)+5) • Generally, an expression can only be referentially transparent if any functions used are pure
  • 14.
    “HIGHER ORDER” FUNCTIONS • Functionsthat take other functions as inputs and/or outputs • Python examples: • Python’s sorted function takes a key parameter, the name of a function that returns a sort key • A GUI button object’s __init__() might be passed the name of the function to be executed when the button is clicked
  • 15.
  • 16.
    SUPPORT FOR FPIN “EVERY DAY” PYTHON • Recursion • Lambda expressions • All functions are “first class functions”, i.e. function names can be • passed as arguments • assigned to variables • appear as elements of lists • etc.
  • 17.
    PYTHON ITERABLES AND ITERATORS •An iterable is an object that defines either a __getitem__() or an __iter__() method • The built-in function iter(iterable) returns an iterator (also returned by generators) • Iterators define a (private) __next__() method, called by the built-in function next(), raising the StopIteration error when nothing is next
  • 18.
    ITERABLE/ITERATOR EXAMPLE >>> aList =[1, 2] #need not be a list >>> aListIter = iter(aList) #possibly >>> next(aListIter)# lazy evaluation 1 >>> next(aListIter)# 2 >>> next(aListIter) StopIteration Traceback …
  • 19.
    Returns an iteratorwhich is the result of applying <function> (of one argument) to successive individual items supplied by <iterable> EXAMPLE: >>> list(map(lambda x: x*x,[1,3,5,7])) [1, 9, 25, 49] map(<function>, <iterable>)
  • 20.
    MAP RETURNS AN ITERATOR >>>seq = map(lambda x: x*x, [1, 3, 5, 7]) >>> seq <map at 0x…> >>> next(seq) 1 >>> next(seq) 9
  • 21.
    applies <function> (oftwo arguments) to the first two items supplied by <iterable>, thus “reducing” them to a single value. This result is supplied, along with the third item from <iterable> thus “reducing” all three items to a single value Note: reduce must be imported from the built-in library functools reduce(<function>, <iterable>)
  • 22.
    WORKING WITH reduce >>>from functools import reduce >>> reduce(lambda x,y: x*y, range(1, 6)) 120 #The product of ((1*2)*3)*4)*5
  • 23.
    Constructs an iteratorfor those elements of <iterable> for which <function> returns True Equivalent to the generator expression (for item in <iterable> item if <function>(item) ) filter(<function>, <iterable>)
  • 24.
    WORKING WITH filter >>>isEven = lambda x: (x%2 == 0) >>> yetAnotherList = [12, 43, 33, 32, 31] >>> it = filter(isEven, yetAnotherList) >>> next(it) 12 >>> next(it) 32 >>> list(filter(isEven, yetAnotherList)) [12, 32]
  • 25.
    AN EXTENDED EXAMPLE min_order= 100 invoice_totals = list( map(lambda x: x if x[1] >= min_order else (x[0], x[1] + 10), map(lambda x: (x[0],x[2] * x[3]), orders)))
  • 26.
    WHAT ELSE ISTHERE TO KNOW? • More jargon (see Wikipedia) • The operator, functools and itertools libraries • Various ideas about pitfalls/best practices
  • 27.
  • 28.
    BIGGEST PITFALL: “It’s thenew kid on the block! • After 60+ years, • Imperative programming (IP) is well understood • Lots of hardware and compiler support for IP • IP code has large code base
  • 29.
    RECURSION IS PROBLEMATIC • Recursionis always relatively slow and memory intensive because of the many subroutine calls usually required • Python, in particular, only grudgingly supports recursion • Implementation not particularly fast • Finite recursion depth, even for tail recursion
  • 30.
    FP “Update asyou copy” PARADIGM PROBLEMATIC • The alternative to updating mutable variables is “update as you copy” • “Update as you copy” leads to memory bloat • Can be difficult to keep track of what’s going on if nested objects are being copied/updated
  • 31.
    FP “Update asyou copy” PARADIGM PROBLEMATIC • The alternative to updating mutable variables is “update as you copy” • “Update as you copy” leads to memory bloat • Can be difficult to keep track of what’s going on if nested objects are being copied/updated
  • 32.
    PURE FUNCTIONS ANDI/O DON’T REALLY MIX • Python functions such as input()are not even close to referentially transparent! • Internal state variables needed for buffered reads/writes
  • 33.
    BEST PRACTICES SPECIFICTO FUNCTIONAL PROGRAMMING • Use map, reduce, filter, and other library functions in place of recursion if possible • Cleanly separate pure functions from intrinsically impure functions (such as I/O) • Combine related functions in a class and use inheritance (yes, FP and OO do mix!)
  • 34.
    BEST PRACTICES APPLICABLETO ALL PYTHON PROGRAMMING • “short and sweet” functions, doing one thing well (1 or 2 ISO/ANSI 24x80 screens) • Take advantage of Python’s polymorphism • Don’t pack too much into a single line of code • Use descriptive variable names
  • 35.
    BEST PRACTICES PER“THE ZEN OF PYTHON” • “… practicality beats purity” • “Readability counts” • “If an implementation is hard to explain, it’s a bad idea” • “If an implementation is easy to explain, it might be a good idea”
  • 36.
    RECAP • Functional programming:definition and advantages • Functional programming: key concepts • Python support for functional programming: the basics and beyond • Potential pitfalls and best practices