This is an introduction to the concepts of purely functional programming in Python. I go over the concepts and techniques of FP, and also talk about specific libraries you can use to help you out with FP.
hundreds of years ago http://yellow5.com/pokey/archive/index264.html — @stevehavelka heard from someone that every great talk starts with a lengthy section on history.
- Pascal, the first programming language, had two features: functions and procedures.
- Functions return, procedures had side-effects.
- we made a distinction then, most modern languages don’t.
“pure” Function, n. (plural functions) A relation between a set of inputs and a set of permissible outputs with the property that each input is related to exactly one output. -Wikipedia - title of this talk is “PURELY functional programming”
- shouldn’t have to say that.
- a distinction was lost: “functions” changed meaning.
-
- I’ve also heard that every great talk has definition slides.
-
- No room for side-effects. Can’t delete money from your bank account.
- Most don’t know/care when they write funcs or procedures.
So, what is pure FP? just programming with functions. :-)
sad REPL syndrome foo.bar(3)? Foo(dbconn, auth) INSERT INTO txn_log (event, owner, value mock.return_value = MockAuthenticationResponse() def foo(val): … print stuff return stuff Q: ever worked on a big project where it’s hard use the REPL?
“understand foo.bar(3)”. Easy?
but class needs DB connobj, authobj -> HTTP req,
and method YOLOs txn logs into DB.
Have mocks in tests, -> 5 minutes to find & put together
End up adding prints to func, try reproduce behavior in testing env.
2. express result = map(comp(f, g), inputs) not result = [] for input in inputs: x = f(g(input)) result.append(x) - FP lets us specify things in a way that exposes *meaning* more clearly
- lots of FP advocates say that FP style is “say what you want, not how to get it”
toolz purity/data >>> from toolz.dicttoolz import assoc >>> assoc({'a': 1}, 'b', 2) {'a': 1, 'b': 2} - here’s the assoc function from earlier. pretty much does what it looks like.
big data (structures) • Zone.actors : [Actor] • Actor.health : int • (Actor.mana, Actor.inventory) • Actor.attack(target) -> reduce target’s health purity/data - e.g.: game simulation with big world.
- thousands of actors, with health/mana/inventory nested inside
- inventory may contain hundreds of items too
- Copying the whole zone is insanity! How do we do this efficiently without mutation?
zone -> zone purity/data from pyrsistent import m, v zone = m(actors=v(m(health=100, mana=100, inventory=v()))) updated_zone = zone.set_in( (‘actors’, 0, ‘health’), 90) - Example of pyrsistent. Let’s see how to change someone’s health.
- What do you do with a new world, though?
- Well, honestly, to be practical you could just overwrite the old world.
- Or you can treat event-handlers as functions of world -> world and continually run them in a purely functional way.
- Magic word here would be FRP, but I’m not going to talk too much about that.
refactoring def compliment(name): return "Oh, %s is a nice name" % (name,) def foo(): print(compliment(input(“Name?”))) purity/effects - extract the part that takes the result of first IO and does the pure calculation based on it.
- This is a trivial example of functional core/imperative shell, but you can extrapolate this idea to a lot of code.
- In practice, a lot of your functions are already pure, there are a lot that aren’t, and could be.
- Thinking in these terms will help you factor your apps much better.
- I encourage you to go back to your code bases, find the side effects, and try to factor them out.
higher-order result = map(comp(f, g), inputs) not result = [] for input in inputs: x = f(g(input)) result.append(x) style - I showed this earlier when talking about expressiveness.
- Seems like a win! 2 HO funcs: map&compose.
- map: apply a func to all elements in a list.
- compose: double-whammy. accepts and returns functions.
compose h = comp(f, g) same as def h(*args, **kwargs): return f(g(*args, **kwargs)) style - FPers love these tiny abstractions, use them to great effect.
- not just shorter lexically: avoids mention “arg” to funcs,*conceptually* simpler (“number of referenced names”).
abstract it away def factorial(n): if n == 0: return 1 else: return n * factorial(n-1) vs def factorial(n): reduce(operator.mul, range(1, n)) style But that implementation is actually pretty gross conceptually as well as performance-wise.
Reduce is powerful.
Reduce usually recursive in FPL, Py = iterative. large inputs = no p.
astronaut map f (head:rest) = f head : map f rest map f [] = [] or def map(f, inputs): if not inputs: return [] return [f(inputs[0])] + map(f, inputs[1:]) style - Parameterize the per-element calculation and create `map`.
- map, reduce, filter are all simple recursion recipes, extracted into HO funcs.
- though recursion is an important part of FP, people prefer not to do it explicitly
- Using the recipes makes it easier and quicker to understand what the code is doing
- Even though Python hates recursion, these abstractions are implemented iteratively so we can use the same style.