SlideShare une entreprise Scribd logo
1  sur  47
Télécharger pour lire hors ligne
Monads for normal people!




   Dustin Getz   https://github.com/dustingetz/monadic-interpreter
   @dustingetz   https://github.com/dustingetz/pymonads
intended audience
•   coders
•   who are comfortable with lambdas
•   who learn by example
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
large codebases are complex
•   Spring, EJB, AspectJ, DI, AOP
•   Common goal: make code look like business
    logic
•   (to varying degrees of success)
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.
Lots of code to follow
•   Pay attention to how the functions compose
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)
what we want to write
def get_loan(name):
   account = get_account(name)
   balance = get_balance(account)
   loan = get_qualified_amount(balance)
   return loan
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()
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'
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
factor! abstract! happy!
def bind(v, f):
   if (v):
       return f(v)
   else:
       return None
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
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
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
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
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
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'")
def get_balance(account):
   if account.id == 1: return (1000000, None)
   elif account.id == 2: return (75000, None)
   else: return (None, "No balance, acct %s" %
                        account.id)


def get_qualified_amount(balance):
   if balance.cash > 200000: return (balance, None)
   else: return (None, "Insufficient funds: %s" %
                        balance.cash)
def bind(mval, mf):
   value = mval[0]
   error = mval[1]
   if not error:
       return mf(value)
   else:
       return mval
def bind(mval, mf):           # mval == (1, None)
   value = mval[0]            # value == 1
   error = mval[1]            # error == None
   if not error:
       return mf(value)       # mf(1)
   else:
       return mval


>>> mval = (1, None)
>>> bind(mval, get_balance)
(100000, None)
def bind(mval, mf):       # mval == (None, "insuf funds")
   value = mval[0]        # value == None
   error = mval[1         # error == "insuf funds"
   if not error:
       return mf(value)
   else:
       return mval        # short circuit


>>> mval = (None, "insuf funds")
>>> bind(mval, get_balance)
(None, "insuf funds")
Business logic didn't change
# error monad
def bind(mv, mf): mf(mv[0]) if mv[0] else mv
def unit(v): return (v, None)


def m_pipe(val, *fns):
   m_val = unit(val)
   for f in fns:
       m_val = bind(m_val, f)
   return m_val


>>> m_pipe(alice, get_account, get_balance,
              get_qualified_amount)
(1000000, None)
Business logic didn't change
# error monad
def bind(mv, mf): mf(mv[0]) if mv[0] else mv
def unit(v): return (v, None)


def m_chain(*fns): ...


>>> get_loan = m_chain(get_account, get_balance,
                         get_qualified_amount)
<fn that composes the fns in order>


>>> get_loan(alice)
(1000000, None)
>>> get_loan(dustin)
(None, "insuf funds")
Business logic didn't change
get_loan = m_chain(get_account, get_balance,
                   get_qualified_amount)


>>> map(get_loan, [alice, dustin, bob])
[(1000000, None), (None, "insuf funds"), (75000, None)]



>>> mmap(get_loan, [alice, bob])
([1000000, 75000], None)


>>> mmap(get_loan, [alice, bob, dustin])
(None, "insuf funds")
here be monads
# maybe monad
def bind(mv, mf): return mf(mv) if mv else None
def unit(v): return v


# error monad
def bind(mv, mf): mf(mv[0]) if mv[0] else mv
def unit(v): return (v, None)
monad comprehensions
# error monad
def bind(mv, mf): mf(mv[0]) if mv[0] else mv
def unit(v): return (v, None)


>>> mv1 = (7, None)
>>> mv2 = bind( mv1,
                lambda x: unit(x))
(7, None)

>>> mv3 = bind( mv2,
                lambda x: unit(x+1))
(8, None)
monad comprehensions
# error monad
def bind(mv, mf): mf(mv[0]) if mv[0] else mv
def unit(v): return (v, None)

>>> mv1 = (7, None); mv2 = (3, None)
>>> mv3 = bind( mv1,
                  lambda x: bind( mv2,
                                 lambda y: unit(x + y)))
(10, None)

>>> mv4 = (None, “error”)
>>> bind( mv3,
         lambda x: bind( mv4,
                            lambda y: unit(x + y)))
(None, “error”)
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
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
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])
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
here comes the fun stuff
•   introduce a few harder monads
•   combine monads to build...
    o   a Clojure parsing DSL
    o   a Python lisp interpreter
writer monad
def unit(v): return (v, [])
def bind(mv, mf): …


def addOne(x):
   val = x+1
   logmsg = "x+1==%s" % val
   return (val, [logmsg])


>>> mv = addOne(7)
(8, ['x+1==8'])
>>> m_chain(addOne, addOne, addOne)(mv)
(11, ['x+1==9', 'x+1==10', 'x+1==11'])
writer monad
def unit(v): return (v, [])
def bind(mv, mf): …


def addOne(x):
    val = x+1
    logmsg = "x+1==%s" % val
    return (val, [logmsg])


>>> addThreeLogged = m_chain(addOne, addOne, addOne)
>>> map(addThreeLogged, [10,20,30])
[(13, ['x+1==11', 'x+1==12', 'x+1==13']),
(23, ['x+1==21', 'x+1==22', 'x+1==23']),
(33, ['x+1==31', 'x+1==32', 'x+1==33'])]
writer monad
def unit(v): return (v, [])
def bind(mv, mf): …


def addOne(x):
    val = x+1
    logmsg = "x+1==%s" % val
    return (val, [logmsg])


>>> mmap(addOne, [1,2,3])
   ([2, 3, 4], ['x+1==2', 'x+1==3', 'x+1==4'])
reader monad
def unit(v): return lambda env: v
def bind(mv, mf): …


def read(key):
     def _(env):
        return env[key]
     return _


>>> mv = read('a')
<a function of env>
>>> mv({'a': 7})
7
>>> mv({'a': 10, 'b': 3})
10
reader monad
def unit(v): return lambda env: v
def bind(mv, mf): …


def read(key):
     def _(env):
        return env[key]
     return _


>>> computation = bind( read('a'),    lambda a:
                   bind( read('b'),   lambda b:
                          unit( a+b   ))
<a function of env>
>>> computation({'a': 7, 'b': 3})
10
reader monad
def unit(v): return lambda env: v
def bind(mv, mf): …


def read(key):
     def _(env):
        return env[key]
     return _


>>> computation = bind( read('a'),    lambda a:
                   bind( read('b'),   lambda b:
                          unit( a+b   ))
<a function of env>
>>> computation({'a': 42, 'b': 1})
43
environment = reader + writer
def unit(v): …
def bind(mv, mf): …


def read(key): lambda env: (env[key], env)
def write(key, val): lambda env: … (None, newEnv)


>>> write('a', 2)
<a function of env>
>>> myenv = {'a': 999, 'b': 999}
>>> write('a', 2)(myenv)
(None, {'a': 2, 'b': 999})
>>> read('a')(myenv)
(999, {'a': 999, 'b': 999})
environment = reader + writer
>>> computation = bind( read('a'),        lambda a:
                 bind( read('b'),         lambda b:
                 bind( write('c', a+b),   lambda _:
                       unit(c+1)          )))
<a function of env>


>>> computation({'a': 7, 'b': 3})
(11, {'a': 7, 'b': 3, 'c': 10})
interpreter = environment + error
def read(key): lambda env: ...         # can fail
def write(key, val): lambda env: ...   # can fail


>>> computation = bind( read('a'),          lambda a:
                 bind( read('b'),           lambda b:
                 bind( write('c', a+b),     lambda _:
                       unit(c+1)           )))


>>> computation({'a': 7, 'b': 3})
( (11, {'a': 7, 'b': 3, 'c': 10}), None)


>>> computation({'b': 3})
( (None, {'b': 3}), "unbound symbol 'a'")
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)
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)
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"
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
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

Contenu connexe

Tendances

Tendances (20)

Dropwizard
DropwizardDropwizard
Dropwizard
 
How Booking.com avoids and deals with replication lag
How Booking.com avoids and deals with replication lagHow Booking.com avoids and deals with replication lag
How Booking.com avoids and deals with replication lag
 
The consequences of sync_binlog != 1
The consequences of sync_binlog != 1The consequences of sync_binlog != 1
The consequences of sync_binlog != 1
 
[네이버오픈소스세미나] Contribution, 전쟁의 서막 : Apache OpenWhisk 성능 개선 - 김동경
[네이버오픈소스세미나] Contribution, 전쟁의 서막 : Apache OpenWhisk 성능 개선 - 김동경[네이버오픈소스세미나] Contribution, 전쟁의 서막 : Apache OpenWhisk 성능 개선 - 김동경
[네이버오픈소스세미나] Contribution, 전쟁의 서막 : Apache OpenWhisk 성능 개선 - 김동경
 
Hashidays London 2017 - Evolving your Infrastructure with Terraform By Nicki ...
Hashidays London 2017 - Evolving your Infrastructure with Terraform By Nicki ...Hashidays London 2017 - Evolving your Infrastructure with Terraform By Nicki ...
Hashidays London 2017 - Evolving your Infrastructure with Terraform By Nicki ...
 
NoSQL @ CodeMash 2010
NoSQL @ CodeMash 2010NoSQL @ CodeMash 2010
NoSQL @ CodeMash 2010
 
Evolution of MySQL Parallel Replication
Evolution of MySQL Parallel Replication Evolution of MySQL Parallel Replication
Evolution of MySQL Parallel Replication
 
Spring Batch Performance Tuning
Spring Batch Performance TuningSpring Batch Performance Tuning
Spring Batch Performance Tuning
 
Kinh nghiệm triển khai Microservices tại Sapo.vn
Kinh nghiệm triển khai Microservices tại Sapo.vnKinh nghiệm triển khai Microservices tại Sapo.vn
Kinh nghiệm triển khai Microservices tại Sapo.vn
 
Advanced Node.JS Meetup
Advanced Node.JS MeetupAdvanced Node.JS Meetup
Advanced Node.JS Meetup
 
Driving containerd operations with gRPC
Driving containerd operations with gRPCDriving containerd operations with gRPC
Driving containerd operations with gRPC
 
The Full MySQL and MariaDB Parallel Replication Tutorial
The Full MySQL and MariaDB Parallel Replication TutorialThe Full MySQL and MariaDB Parallel Replication Tutorial
The Full MySQL and MariaDB Parallel Replication Tutorial
 
Introduction to Redis
Introduction to RedisIntroduction to Redis
Introduction to Redis
 
HazelCast
HazelCastHazelCast
HazelCast
 
High Concurrency Architecture at TIKI
High Concurrency Architecture at TIKIHigh Concurrency Architecture at TIKI
High Concurrency Architecture at TIKI
 
Process builder vs Triggers
Process builder vs TriggersProcess builder vs Triggers
Process builder vs Triggers
 
Advanced Ops Manager Topics
Advanced Ops Manager TopicsAdvanced Ops Manager Topics
Advanced Ops Manager Topics
 
Load Balancing and Scaling with NGINX
Load Balancing and Scaling with NGINXLoad Balancing and Scaling with NGINX
Load Balancing and Scaling with NGINX
 
Apache HBase Performance Tuning
Apache HBase Performance TuningApache HBase Performance Tuning
Apache HBase Performance Tuning
 
RedisConf17- Using Redis at scale @ Twitter
RedisConf17- Using Redis at scale @ TwitterRedisConf17- Using Redis at scale @ Twitter
RedisConf17- Using Redis at scale @ Twitter
 

Similaire à Monads in python

Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Paulo Ragonha
 
The Art Of Readable Code
The Art Of Readable CodeThe Art Of Readable Code
The Art Of Readable Code
Baidu, Inc.
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin
 
Phoenix for laravel developers
Phoenix for laravel developersPhoenix for laravel developers
Phoenix for laravel developers
Luiz Messias
 
Ruby Language - A quick tour
Ruby Language - A quick tourRuby Language - A quick tour
Ruby Language - A quick tour
aztack
 

Similaire à Monads in python (20)

Indexing thousands of writes per second with redis
Indexing thousands of writes per second with redisIndexing thousands of writes per second with redis
Indexing thousands of writes per second with redis
 
Functional programming in ruby
Functional programming in rubyFunctional programming in ruby
Functional programming in ruby
 
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and JasmineRails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
 
Odoo from 7.0 to 8.0 API
Odoo from 7.0 to 8.0 APIOdoo from 7.0 to 8.0 API
Odoo from 7.0 to 8.0 API
 
Odoo - From v7 to v8: the new api
Odoo - From v7 to v8: the new apiOdoo - From v7 to v8: the new api
Odoo - From v7 to v8: the new api
 
Micropatterns
MicropatternsMicropatterns
Micropatterns
 
A Few of My Favorite (Python) Things
A Few of My Favorite (Python) ThingsA Few of My Favorite (Python) Things
A Few of My Favorite (Python) Things
 
Drinking the free kool-aid
Drinking the free kool-aidDrinking the free kool-aid
Drinking the free kool-aid
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
 
The Art Of Readable Code
The Art Of Readable CodeThe Art Of Readable Code
The Art Of Readable Code
 
The Power of Decorators in Python [Meetup]
The Power of Decorators in Python [Meetup]The Power of Decorators in Python [Meetup]
The Power of Decorators in Python [Meetup]
 
SOLID Ruby, SOLID Rails
SOLID Ruby, SOLID RailsSOLID Ruby, SOLID Rails
SOLID Ruby, SOLID Rails
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
Phoenix for laravel developers
Phoenix for laravel developersPhoenix for laravel developers
Phoenix for laravel developers
 
Refactor like a boss
Refactor like a bossRefactor like a boss
Refactor like a boss
 
Type safe embedded domain-specific languages
Type safe embedded domain-specific languagesType safe embedded domain-specific languages
Type safe embedded domain-specific languages
 
Mining Functional Patterns
Mining Functional PatternsMining Functional Patterns
Mining Functional Patterns
 
Beware sharp tools
Beware sharp toolsBeware sharp tools
Beware sharp tools
 
Ruby Language - A quick tour
Ruby Language - A quick tourRuby Language - A quick tour
Ruby Language - A quick tour
 
Python speleology
Python speleologyPython speleology
Python speleology
 

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
  • 12. factor! abstract! happy! def bind(v, f): if (v): return f(v) else: return None
  • 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'")
  • 19. def get_balance(account): if account.id == 1: return (1000000, None) elif account.id == 2: return (75000, None) else: return (None, "No balance, acct %s" % account.id) def get_qualified_amount(balance): if balance.cash > 200000: return (balance, None) else: return (None, "Insufficient funds: %s" % balance.cash)
  • 20. def bind(mval, mf): value = mval[0] error = mval[1] if not error: return mf(value) else: return mval
  • 21. def bind(mval, mf): # mval == (1, None) value = mval[0] # value == 1 error = mval[1] # error == None if not error: return mf(value) # mf(1) else: return mval >>> mval = (1, None) >>> bind(mval, get_balance) (100000, None)
  • 22. def bind(mval, mf): # mval == (None, "insuf funds") value = mval[0] # value == None error = mval[1 # error == "insuf funds" if not error: return mf(value) else: return mval # short circuit >>> mval = (None, "insuf funds") >>> bind(mval, get_balance) (None, "insuf funds")
  • 23. Business logic didn't change # error monad def bind(mv, mf): mf(mv[0]) if mv[0] else mv def unit(v): return (v, None) def m_pipe(val, *fns): m_val = unit(val) for f in fns: m_val = bind(m_val, f) return m_val >>> m_pipe(alice, get_account, get_balance, get_qualified_amount) (1000000, None)
  • 24. Business logic didn't change # error monad def bind(mv, mf): mf(mv[0]) if mv[0] else mv def unit(v): return (v, None) def m_chain(*fns): ... >>> get_loan = m_chain(get_account, get_balance, get_qualified_amount) <fn that composes the fns in order> >>> get_loan(alice) (1000000, None) >>> get_loan(dustin) (None, "insuf funds")
  • 25. Business logic didn't change get_loan = m_chain(get_account, get_balance, get_qualified_amount) >>> map(get_loan, [alice, dustin, bob]) [(1000000, None), (None, "insuf funds"), (75000, None)] >>> mmap(get_loan, [alice, bob]) ([1000000, 75000], None) >>> mmap(get_loan, [alice, bob, dustin]) (None, "insuf funds")
  • 26. here be monads # maybe monad def bind(mv, mf): return mf(mv) if mv else None def unit(v): return v # error monad def bind(mv, mf): mf(mv[0]) if mv[0] else mv def unit(v): return (v, None)
  • 27. monad comprehensions # error monad def bind(mv, mf): mf(mv[0]) if mv[0] else mv def unit(v): return (v, None) >>> mv1 = (7, None) >>> mv2 = bind( mv1, lambda x: unit(x)) (7, None) >>> mv3 = bind( mv2, lambda x: unit(x+1)) (8, None)
  • 28. monad comprehensions # error monad def bind(mv, mf): mf(mv[0]) if mv[0] else mv def unit(v): return (v, None) >>> mv1 = (7, None); mv2 = (3, None) >>> mv3 = bind( mv1, lambda x: bind( mv2, lambda y: unit(x + y))) (10, None) >>> mv4 = (None, “error”) >>> bind( mv3, lambda x: bind( mv4, lambda y: unit(x + y))) (None, “error”)
  • 29. 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
  • 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
  • 34. writer monad def unit(v): return (v, []) def bind(mv, mf): … def addOne(x): val = x+1 logmsg = "x+1==%s" % val return (val, [logmsg]) >>> mv = addOne(7) (8, ['x+1==8']) >>> m_chain(addOne, addOne, addOne)(mv) (11, ['x+1==9', 'x+1==10', 'x+1==11'])
  • 35. writer monad def unit(v): return (v, []) def bind(mv, mf): … def addOne(x): val = x+1 logmsg = "x+1==%s" % val return (val, [logmsg]) >>> addThreeLogged = m_chain(addOne, addOne, addOne) >>> map(addThreeLogged, [10,20,30]) [(13, ['x+1==11', 'x+1==12', 'x+1==13']), (23, ['x+1==21', 'x+1==22', 'x+1==23']), (33, ['x+1==31', 'x+1==32', 'x+1==33'])]
  • 36. writer monad def unit(v): return (v, []) def bind(mv, mf): … def addOne(x): val = x+1 logmsg = "x+1==%s" % val return (val, [logmsg]) >>> mmap(addOne, [1,2,3]) ([2, 3, 4], ['x+1==2', 'x+1==3', 'x+1==4'])
  • 37. reader monad def unit(v): return lambda env: v def bind(mv, mf): … def read(key): def _(env): return env[key] return _ >>> mv = read('a') <a function of env> >>> mv({'a': 7}) 7 >>> mv({'a': 10, 'b': 3}) 10
  • 38. reader monad def unit(v): return lambda env: v def bind(mv, mf): … def read(key): def _(env): return env[key] return _ >>> computation = bind( read('a'), lambda a: bind( read('b'), lambda b: unit( a+b )) <a function of env> >>> computation({'a': 7, 'b': 3}) 10
  • 39. reader monad def unit(v): return lambda env: v def bind(mv, mf): … def read(key): def _(env): return env[key] return _ >>> computation = bind( read('a'), lambda a: bind( read('b'), lambda b: unit( a+b )) <a function of env> >>> computation({'a': 42, 'b': 1}) 43
  • 40. environment = reader + writer def unit(v): … def bind(mv, mf): … def read(key): lambda env: (env[key], env) def write(key, val): lambda env: … (None, newEnv) >>> write('a', 2) <a function of env> >>> myenv = {'a': 999, 'b': 999} >>> write('a', 2)(myenv) (None, {'a': 2, 'b': 999}) >>> read('a')(myenv) (999, {'a': 999, 'b': 999})
  • 41. environment = reader + writer >>> computation = bind( read('a'), lambda a: bind( read('b'), lambda b: bind( write('c', a+b), lambda _: unit(c+1) ))) <a function of env> >>> computation({'a': 7, 'b': 3}) (11, {'a': 7, 'b': 3, 'c': 10})
  • 42. interpreter = environment + error def read(key): lambda env: ... # can fail def write(key, val): lambda env: ... # can fail >>> computation = bind( read('a'), lambda a: bind( read('b'), lambda b: bind( write('c', a+b), lambda _: unit(c+1) ))) >>> computation({'a': 7, 'b': 3}) ( (11, {'a': 7, 'b': 3, 'c': 10}), None) >>> computation({'b': 3}) ( (None, {'b': 3}), "unbound symbol 'a'")
  • 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