Lessons from Dynamic Languages Radical Simplicity == Lessons from Dynamic Languages Joe Gregorio IBM WebSphere Technology Institute Rules of Engagement Interrupt at any time Disclaimer All examples are in Python ---- True in many other languages: Perl, PHP, JavaScript, Ruby, Smalltalk, ... Shape Matters {{{ Aoccdrnig to a rscheearch at Cmabrigde Uinervtisy, it deosn't mttaer in waht oredr the ltteers in a wrod are, the olny iprmoetnt tihng is taht the frist and lsat ltteer be at the rghit pclae. The rset can be a toatl mses and you can sitll raed it wouthit porbelm. Tihs is bcuseae the huamn mnid deos not raed ervey lteter by istlef, but the wrod as a wlohe. }}} ---- [http://www.snopes.com/language/apocryph/cambridge.asp Snopes] Shape Matters Not completely true. ---- It's not just the first and last letters. ---- But it does indicate how to make something unreadable. Shape Matters {{{ m_According m_to m_a m_researcher m_at m_Cambridge m_University, m_it m_doesn't m_matter m_in m_what m_order m_the m_letters m_in m_a m_word m_are, m_the m_only m_important m_thing m_is m_that m_the m_first and m_last m_letter m_be m_at m_the m_right m_place. m_The m_rest m_can m_be m_a m_total m_mess m_and m_you m_can m_still m_read m_it m_without m_problem. m_This m_is m_because m_the m_human m_mind m_does m_not m_read m_every m_letter m_by m_itself, m_but m_the m_word m_as m_a m_whole. }}} ---- So much for Hungarian Notation Syntax is Shape You agree with me. ---- Even if you program in C, C# or Java. ---- How do I know that? ---- Tell me the one true way to place my braces: Braces are Shape
{{{ if (foo) { doFoo(); } else { doBar(); } }}}
{{{ if (foo) { doFoo(); } else { doBar(); } }}}
{{{ if (foo) { doFoo(); } else { doBar(); } }}}
Braces are Shape Feel free to settle this with fisticuffs in the hall after the session. Braces are Shape Of course, the right answer in C is... ---- {{{ foo ? doFoo() : doBar(); }}} Python and Whitespace Defines the shape of the code. ---- {{{ def fib(n): # write Fibonacci series up to n a, b = 0, 1 while b < n: print b, a, b = b, a+b }}} Python and Whitespace Hardly a straight jacket ---- {{{>>> def f( a,b ): return ( a- b ) >>> f(2,3) -1 >>>}}} ---- It is possible, but not easy, to write ugly Python code. Abstractions Matter Abstractions shape code. First-class functions {{{ >>> def f(x): return x*x }}}----{{{ >>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }}}----{{{ >>> map(f, range(10)) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] }}} Keyword parameters Parameters can be specified by name and not just order. {{{ >>> def f(a, b): return a+b }}}----{{{ >>> f(b=5, a=7) 12 }}} Keyword Parameters {{{ >>> def f(a,b): return a+b ---- >>> p = {'a': 10, 'b': 100} ---- >>> f(**p) 110 }}} Default parameters Defaults can be supplied for parameters. {{{ >>> def f(a, b=100): return a + b }}}----{{{ >>> f(2) 102 }}} Parallel assignment using tuples A tuple is an immutable list and can be used in assignment. {{{ >>> a = 1 >>> b = 2 >>> (c, d) = (a+b, a-b) >>> c 3 >>> d -1 }}} Efficient multiple return values Tuples can also be used to return multiple values. {{{ >>> def f(a, b): return (a-b, a+b) >>> f(2,3) (-1, 5) }}} Closures {{{>>> def too_big(limit): def compare(x): return x > limit return compare}}} ----{{{>>> f = too_big(100) }}}----{{{ >>> f(100) False }}}----{{{ >>> f(101) True }}} Meta-programming {{{ >>> class A: b = 3 }}} ---- {{{ >>> a = A() >>> a.b 3 }}} Meta-programming {{{ >>> def f(self, a): return a + self.b }}} ---- {{{ >>> A.fred = f }}} ---- {{{ >>> a.fred(4) 7 }}} By Example URI dispatching in Django. {{{ "/article/(?P<year>\d{4})/$" ---- --> yearly_archives(year) ---- "/tag/(?P<tag>\w+)/$" --> topic_archives(tag) }}} Example {{{ def yearly_archives(year): print "The archives from %s" % year def topic_archives(tag): print "Articles tagged with %s" % tag }}} Selector {{{ selector = [ ("/article/(?P<year>\d{4})/$", yearly_archives), ("/tag/(?P<tag>\w+)/$", topic_archives) ] }}} Dispatch {{{ >>> dispatch(selector, "/article/2006") The archives from 2006 }}} Implementation {{{ import re def dispatch(selector, uri): for (regex, callable) in selector: match = re.search(regex, uri) if match: callable(**match.groupdict()) break }}} Implementation {{{ import re def dispatch(selector, uri): for (regex, callable) in selector: match = re.search(regex, uri) if match: callable(**match.groupdict()) break }}} Selector is an array of ''tuples''. Those tuples contain ''first class functions''. Implementation {{{ import re def dispatch(selector, uri): for (regex, callable) in selector: match = re.search(regex, uri) if match: callable(**match.groupdict()) break }}} Tuples used in parallel assignment. Implementation {{{ import re def dispatch(selector, uri): for (regex, callable) in selector: match = re.search(regex, uri) if match: callable(**match.groupdict()) break }}} Keyword parameters. Laws of Simplicity [http://lawsofsimplicity.com Laws of Simplicity] {{{ Reduce Organize Time Learn Differences Context Emotion Trust Failure The One }}} Reduce ---- No semicolons ---- No {}'s ---- No compiling Organize ---- Shape---- (Whitespace) ---- Shape (Abstractions) Time ---- Less typing ---- Skip the compile The One "Simplicity is about subtracting the obvious, and adding the meaningful." {{{ import re def dispatch(selector, uri): for (regex, callable) in selector: match = re.search(regex, uri) if match: callable(**match.groupdict()) break }}} Web Frameworks Python has a lot of them. ---- Why? ---- Too easy to build Components {{{ Templating: ---- [http://kid-templating.org/ Kid] ---- SQL: ---- [http://www.sqlalchemy.org/ SQLAlchemy] ---- Routing: ---- [http://lukearno.com/projects/selector/ Selector] ---- Glue: ---- [http://www.python.org/dev/peps/pep-0333/ WSGI] }}} WSGI {{{ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler def simple_app(environ, start_response): start_response('200 OK', [('Content-type','text/html')]) return ['<h1>Hello World</h1>'] httpd = WSGIServer(("", 8080), WSGIRequestHandler) httpd.set_app(simple_app) print "Serving HTTP on %s port %s ..." % httpd.socket.getsockname() httpd.serve_forever() }}} WSGI {{{ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler def simple_app(environ, start_response): start_response('200 OK', [('Content-type','text/html')]) return ['<h1>Hello World</h1>'] httpd = WSGIServer(("", 8080), WSGIRequestHandler) httpd.set_app(simple_app) print "Serving HTTP on %s port %s ..." % httpd.socket.getsockname() httpd.serve_forever() }}} A WSGI application is a callable object that supports this signature. WSGI {{{ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler def simple_app(environ, start_response): start_response('200 OK', [('Content-type','text/html')]) return ['<h1>Hello World</h1>'] httpd = WSGIServer(("", 8080), WSGIRequestHandler) httpd.set_app(simple_app) print "Serving HTTP on %s port %s ..." % httpd.socket.getsockname() httpd.serve_forever() }}} Start_response() sets the status code and any response headers. WSGI {{{ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler def simple_app(environ, start_response): start_response('200 OK', [('Content-type','text/html')]) return ['<h1>Hello World</h1>'] httpd = WSGIServer(("", 8080), WSGIRequestHandler) httpd.set_app(simple_app) print "Serving HTTP on %s port %s ..." % httpd.socket.getsockname() httpd.serve_forever() }}} The return from the callable is an iterable. WSGI Most of that is true. ---- There are details. ---- Read PEP 333 for all the details. ---- For example: Middleware Model Model/View/Controller ---- Model/View/'''Template/Dispatcher''' Model {{{ +-> Model Disptacher -> View -+ +-> Template }}} File Layout model.py ---- view.py ---- urls.py ---- templates/* model.py {{{ from sqlalchemy import Table, Column, String import dbconfig entry_table = Table('entry', dbconfig.metadata, Column('id', String(100), primary_key=True), Column('title', String(100)), Column('content', String(30000)), Column('updated', String(20), index=True) ) }}} dbconfig.py {{{ from sqlalchemy import * metadata = BoundMetaData('sqlite:///tutorial.db') }}} manage.py {{{ import os, sys def create(): from sqlalchemy import Table import model for (name, table) in vars(model).iteritems(): if isinstance(table, Table): table.create() if __name__ == "__main__": if 'create' in sys.argv: create() }}} Create tables {{{ $ python manage.py create }}} urls.py {{{ import selector import view urls = selector.Selector() urls.add('/blog/', GET=view.list) urls.add('/blog/{id}/', GET=view.member_get) }}} view.py {{{ import robaccia import model def list(environ, start_response): rows = model.entry_table.select().execute() return robaccia.render(start_response, 'list.html', locals()) def member_get(environ, start_response): id = environ['selector.vars']['id'] row = model.entry_table.select(model.entry_table.c.id==id ).execute().fetchone() return robaccia.render(start_response, 'entry.html', locals()) ... }}} robaccia.py {{{ def render(start_response, template_file, vars): ext = template_file.rsplit(".") contenttype = "text/html" if len(ext) > 1 and (ext[1] in extensions): contenttype = extensions[ext[1]] template = kid.Template(file=os.path.join( 'templates', template_file), **vars) body = template.serialize(encoding='utf-8') start_response("200 OK", [('Content-Type', contenttype)]) return [body] }}} templates/list.html {{{ <?xml version="1.0" encoding="utf-8"?> <html xmlns:py="http://purl.org/kid/ns#>"> <head> <title>A Robaccia Blog</title> </head> <div py:for="row in rows.fetchall()"> <h2>${row.title}</h2> <div>${row.content}</div> <p><a href="./${row.id}/">${row.updated}</a></p> </div> </html> }}} manage.py {{{ def run(): import urls from wsgiref.simple_server import WSGIServer, WSGIRequestHandler httpd = WSGIServer(("", 8080), WSGIRequestHandler) httpd.set_app(urls.urls) print "Serving HTTP on %s port %s ..." % httpd.socket.getsockname() httpd.serve_forever() if __name__ == "__main__": if 'create' in sys.argv: create() if 'run' in sys.argv: run() }}} Running {{{ $ python manage.py run Serving HTTP on 0.0.0.0 port 8080 ... }}} main.cgi {{{ #!/usr/bin/python import manage manage.run() }}} Touch Points How much special glue code did we need for: ---- Template (Kid) <-> Model (SQLAlchemy) ---- View <-> Dispatch (Selector) ---- Dispatch (Selector) <-> Model ---- {{{id = environ['selector.vars']['id']}}} Development Environment === VI + F5 What is simple The underlying technologies are ''close to the surface'' SQL SQLAlchemy ---- 'Just' a wrapper around SQL, not an object persistence layer. ---- You can even get 'closer' to the SQL. ---- {{{ t = engine.text("select foo from mytable where lala=:hoho") r = t.execute(hoho=7) }}} HTML Kid template is XHTML. ---- {{{ <?xml version="1.0" encoding="utf-8"?> <html xmlns:py="http://purl.org/kid/ns#>"> <head> <title>A Robaccia Blog</title> </head> <div py:for="row in rows.fetchall()"> <h2>${row.title}</h2> <div>${row.content}</div> <p><a href="./${row.id}/">${row.updated}</a></p> </div> </html> }}} Routing Selector is just a piece of WSGI middleware. ---- View is just a WSGI application. ---- WSGI keeps you 'close' to HTTP. Domain Specific Languages Languages used in preparing this presentation: ---- HTML ---- CSS ---- Python ---- WikiML Domain Specific Languages