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