This document introduces monads for programmers who are familiar with lambdas. It uses a bank API example to demonstrate how monads can be used to compose functions in a cleaner way that avoids null pointer exceptions. The code is refactored using a bind function to chain the API calls together in a way that looks like the business logic. This introduces the reader to how monads can increase modularity and manage complexity in large codebases.
1 of 47
Downloaded 96 times
More Related Content
Monads in python
1. Monads for normal people!
Dustin Getz https://github.com/dustingetz/monadic-interpreter
@dustingetz https://github.com/dustingetz/pymonads
2. intended audience
coders
who are comfortable with lambdas
who learn by example
3. goals
how do monads work
how do monads help
are monads useful IRL?
especially in enterprise?
where do they fall short and what's next
4. large codebases are complex
Spring, EJB, AspectJ, DI, AOP
Common goal: make code look like business
logic
(to varying degrees of success)
5. Aspect Oriented Programming
From Wikipedia, the free encyclopedia
In computing, aspect-oriented programming (AOP) is a
programming paradigm which aims to increase modularity by allowing the
separation of cross-cutting concerns.
Typically, an aspect is scattered or tangled as code, making it harder to
understand and maintain. It is scattered by virtue of the function (such as
logging) being spread over a number of unrelated functions that might use
its function, possibly in entirely unrelated systems, different source
languages, etc. That means to change logging can require modifying all
affected modules. Aspects become tangled not only with the mainline
function of the systems in which they are expressed but also with each
other. That means changing one concern entails understanding all the
tangled concerns or having some means by which the effect of changes can
be inferred.
6. Lots of code to follow
Pay attention to how the functions compose
7. a bank API
def get_account(person):
if person.name == "Alice": return 1
elif person.name == "Bob": return 2
else: return None
def get_balance(account):
if account.id == 1: return 1000000
elif account.id == 2: return 75000
else: return None
def get_qualified_amount(balance):
if balance.cash > 200000: return balance.cash
else: return None
(shoutout to Tumult on hackernews for the 'bank' example)
8. what we want to write
def get_loan(name):
account = get_account(name)
balance = get_balance(account)
loan = get_qualified_amount(balance)
return loan
9. My boss could write this code
get_qualified_amount( get_balance( get_account( alice )))
alice | get_account | get_balance | get_qualified_amount
(-> get_account get_balance get_qualified_amount)(alice)
alice.get_account().get_balance().get_qualified_amount()
10. i love NullPointerExceptions
def get_account(person):
if person.name == "Alice": return 1
elif person.name == "Bob": return 2
else: return None
>>> get_account(None)
AttributeError: 'NoneType' object has no attribute 'id'
11. what the prod code looks like :(
def get_loan(person):
account = get_account(person)
if not account:
return None
balance = get_balance(account)
if not balance:
return None
loan = get_qualified_amount(balance)
return loan
13. factor! abstract! happy!
def bind(v, f):
if (v): # v == alice
return f(v) # get_account(alice)
else:
return None
>>> alice = Person(...)
>>> bind(alice, get_account)
1
14. factor! abstract! happy!
def bind(v, f):
if (v):
return f(v)
else: # v == None
return None # don't call f
>>> bind(None, get_account)
None
15. the code we really want to write
def bind(v,f): return f(v) if v else None
def get_loan(name):
m_account = get_account(name)
m_balance = bind(m_account, get_balance)
m_loan = bind(m_balance, get_qualified_amount)
return m_loan
>>> alice = Person(...)
>>> get_loan(alice)
100000
>>> get_loan(None)
None
16. or more succinctly
def bind(v,f): return f(v) if v else None
def m_pipe(val, fns):
m_val = val
for f in fns:
m_val = bind(m_val, f)
return m_val
>>> fns = [get_account, get_balance, get_qualified_amount]
>>> m_pipe(alice, fns)
1000000
>>> m_pipe(dustin, fns)
None
17. big picture goal
make the code look like the business logic
"good clojure programmers write a language to write
their program in" -- DSLs
build a language to build your business logic
add features without changing your business logic
18. add a feature to our API
def get_account(person):
if person.name == "Alice": return (1, None)
elif person.name == "Bob": return (2, None)
else: return (None, "No acct for '%s'" % person.name)
>>> get_account(alice)
(1, None)
>>> get_account(dustin)
(None, "No acct for 'Dustin'")
30. the simplest monad
# identity monad
def bind(mv,mf): return mf(mv)
def unit(v): return v
>>> bind( 7, lambda x:
bind( 3, lambda y:
unit(x + y)))
10
repl> (let [x 7
y 3]
x + y)
10
31. Lists are a monad!
>>> [(x,y) for x in ranks for y in files]
# list monad
def unit(v): return [v]
def bind(mv,mf): return flatten(map(mf, mv))
>>> ranks = list("abcdefgh")
>>> files = list("12345678")
>>> bind( ranks, lambda rank:
bind( files, lambda file:
unit((rank, file))))
repl> (for [rank (seq abcdefgh)
file (seq 12345678)]
[rank, file])
32. monads are...
a design pattern for composing functions
that have incompatible types, but can still
logically define composition
by overriding function application
to increase modularity and manage
accidental complexity
33. here comes the fun stuff
introduce a few harder monads
combine monads to build...
o a Clojure parsing DSL
o a Python lisp interpreter
43. interpreter = environment + error
lis.py> 1
((1, {}), None)
lis.py> x
((None, {}), 'referenced unbound symbol x')
lis.py> (define x 1)
((None, {'x': 1}), None)
lis.py> x
((1, {'x': 1}), None)
lis.py> (+ x 2)
((3, {'x': 1}), None)
lis.py> (set! x (+ x 2))
((None, {'x': 5}), None)
lis.py> x
((5, {'x': 5}), None)
44. interpreter = environment + error
lis.py> +
((<function <lambda> at 0x10047f320>,
{
'+': <function <lambda> at 0x10047f320>
'*': <function <lambda> at 0x10047f398>,
'dumpenv': <function <lambda> at 0x10047ecf8>,
'assert': <function <lambda> at 0x10047ec80>,
'symbol?': <function <lambda> at 0x10047eed8>,
'eq?': <function <lambda> at 0x10047f1b8>,
'car': <function <lambda> at 0x10047f2a8>,
... snip ...
}), None)
45. interpreter = monad heaven
identity -> values
reader -> read-only globals (builtins)
environment -> def, set!, lambda (lexical
scope)
error -> assert, throw
continuation -> call/cc, try/catch/finally
"monads are how you implement special forms"
46. parser = environment + maybe
env where you are in the input, not a map
`nil` indicates failed parse branch, discard
current env, restore env to last successful
parse point and try another branch
47. So who cares?
Monads are a tool to manage complexity
Write code with maximally separated
concerns
trust that you won't break existing code,
allow you to maintain with minimal changes
write a language to talk about business state
transitions, write your business logic in that
language