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.
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.
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. - <click> - I’ve also heard that every great talk has definition slides. - <read> - 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. :-)
this for fun - not just encouraging you to expand your mind - people just go back to familiarity - YOLO state manipulation and OO design are a big source of problems - pure FP seriously improves our code
me tell you about why FP is good for you. - <click> Reason about code in a way impossible with YOLO. - <click> express code concisely/meaningfully. abstract mechanisms for composing funcs. - <click> WAY easier to test. Call func with args, check result. - <click> Opportunity for speed for PFP, parallelism
>>> f(1, 3) “Everything is awesome!” - This is what it’s like to experiment and trace execution. - It’s just like when you learned Python. Try stuff out. - May seem trite. Surely, anyone can do this kind of stuff with their Python code?
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? <click> “understand foo.bar(3)”. Easy? <click> but class needs DB connobj, authobj -> HTTP req, <click> and method YOLOs txn logs into DB. <click> Have mocks in tests, -> 5 minutes to find & put together <click> End up adding prints to func, try reproduce behavior in testing env. Happens to me! All the time! It’s a tragedy! slows dev cycle.
most code PF, you CAN use the REPL. - Functional code naturally minimizes deps between comps - end up w/ wonderfully factored little funcs, easy to evaluate in REPL.
[] 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” - it’s rhetoric, but has a grain of truth - more on this later when I talk about “style”
its return value. - Call func w/ args, check return. That’s it. - Not how most Python code is tested these days. - Mocking has become absolute *nightmare*. mocks -> mocks -> mocks - need track complex YOLOs - Tests too coupled to implementation. very hard to change your code. - care about this a lot. Really into unit testing. I like 100% test coverage. - I still mock (or parameterize deps), but the mocks are much more tasteful in FP
Watch PyPy’s STM - STM optimistically runs your code concurrently - PFP = trivially parallel. - STM needs more work, but it’s the future. So much that it hurts. And so is FP! - Basically, STM and PFP will kill the GIL.
v return new purity - local YOLO often necessary when doing pure FP in Python. - This function replaces a value in a dictionary by copying and mutating. - Pure FP, but local mutation. Easiest way to do it in this case.
data structures for FP. - Get stuff done by explicit copy/modify, but it’s inefficient and error-prone. - (e.g. nested mutable objects) - <click> toolz is a nice lib that supplies useful functions.
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?
a) purity/data - before I explain pyrsistent, an example of the idea of shared structure - pushing/popping a linked list is very efficient. Structure is shared.
is also essentially O(1) (O(log32 N))! - only the nodes on the way to that chunk need to be allocated - all shared structure just gets referenced in the new vector. - brown is old, blue is new.
= 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.
is just a bunch of faffing about. Can’t do _real_ programming with pure FP!” - Useful programs have to communicate with the outside world. - You’re right. But maybe we can still do something useful
a nice name" % (name,)) purity/effects - Here’s a typical IO-using function. - It looks tiny, right? - Let’s reduce it to its essence. Which parts can be factored out to be pure?
% (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.
@do def foo(): name = yield Input(“Name?”) return Print(compliment(n)) or return eff.on(lambda n: Print(compliment(n))) - Here’s an idea. - represent our side-effects as objects describing *intent* to perform an effect. - input became an Input object, print became a Print object. - `on` attaches a callback to deal with the eventual result of that IO. - This is a liiiiittle but of a tedious syntax, so… <click> - Maybe we can take advantage of generator syntax to make it look nicer. - This is just like asyncio’s coroutines. - twisted also has inlineCallbacks
Print(compliment(n)) def main(): perform(foo()) - So what’s the point? - When we call it, we get back an introspectable object. - Test it. - In the end, actually perform the side-effects with a “perform” function.
minor syntactic differences, this is exactly how Effect works. - It’s rather extreme compared to the typical Python program - but similar to asyncio, Twisted, etc. - Not just for IO-based side-effects. also useful for RNG, mutable data writes, etc.
from it; all things return to it. —Tao Te FP - That’s all I’m going to say about purity and side-effects. - Could say a lot more, but TIME. - Now I want to talk about the *style* of functional programming. - Lisp, Erlang, OCaml, are all about stylistic FP. They don’t isolate side-effects, but they do love everything-as-functions.
• don’t reassign • be an abstraction astronaut style - “higher-order” DEFINE - Use functions for control flow. Ex, recursion == looping. - HO functions are used for control flow and abstraction - Don’t reassign variables - already talked about this - combine above = abstraction astronaut.
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.
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”).
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.
add1all [] = [] or, def add1all(inputs): if not inputs: return [] return [inputs[0] + 1] + add1all(inputs[1:]) style - Functional programmers love abstractions. - Many patterns of recursion have been identified. - In ancient history, people often wrote things like this, and realized a pattern
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.
the end! - functional purity: idea of not exposing side-effects - functional style: use functions for everything! - doing pure functional programming leads to - easier reading - easier adding features/fixing bugs - easier refactoring - easier testing - Go forth and invoke a function!