Arcish Web Programming In Python

02 Feb 2008

There seems to be little more sanity coming to the Arc discussion. Some people are still paniced about HTML generation, but I’m not sure that you really have to use that if you don’t like it. Others are just disappointed because they were hoping for … I don’t know what, some sort of Holy Grail, but it’s, y’know, just a programming language.

The terseness it brings to common web expressions is quite pleasant though, and I wanted to be able to have a similar level of power and terseness in Python if possible. I spent a little time putting together “pyflow” (named for both the nicer control-flow primitives and, hopefully, a state of mind for its users). The “Arc Challenge” code looks like:

def said(req):
    def result(foo):
        cede(link(text("you said: " + foo),
                  "click here"))
    cede(aform(result, input("foo")))

It’s 19 tokens (which is 4 more than the Arc version), but I still find it nice to read. I prefer “foo” being passed as a real parameter (vs. (arg _ “foo”) in Arc), but the cede()’s are somewhat less pleasant on the Python side.

Moving to a reimplementation of blog.arc, pyflow fares better.

Posts = getstorelist("posts")
BlogTitle = "A Blog"

def blogPage(body):
    return defpage(fixed600(
                    tag("h1", link("blog", BlogTitle)),
                    withsepbull(link("archive"), link("newpost", "new post"))),

def permalink(p): return "viewpost?id=%d" % p["id"]
def notfound(): return blogPage(div("spacedp", "No such post."))

def displayPost(p, user):
    head = tag("h2", link(permalink(p), p["title"]))
    user = " " + link("editpost?id=" + str(p["id"]), "[edit]") if user else ""
    return head + user + div("spacedp", markdown(p["body"]))
def blog(req):
    user = getUser(req)
    return blogPage(flat([displayPost(p, user) for p in reversed(Posts[-5:])]))
def archive(req):
    links = []
    for p in reversed(Posts):
        links.append(tag("li", link(permalink(p), p["title"])))
    return blogPage(ul(*links))

def viewpost(req):
    id = req.getint('id')
    user = getUser(req)
    if id and id     else: return notfound()

def newpost(req):
    def res(t, b):
        p = {'id': len(Posts), 'title': t, 'body': b}
            aform(res, withsepbr(
                input("t", "", 60),
                textarea("b", "", 10, 80) + br())),
            title="new post"))
def editpost(req):
    id = req.getint('id')
    def res(t, b):
        Posts[id]['title'] = t
        Posts[id]['body'] = b
        aform(res, withsepbr(
            input("t", Posts[id]["title"], 60),
            textarea("b", Posts[id]["body"], 10, 80) + br())),
        title="edit post"))

Kind of long for Wordpress’s asinine (non-)code handling, but quite short given the functionality that’s in there. (I got lazy on editpost, it should really share code with newpost). Wins over the Arc code include:

Simple persistent list/dict built in. A lot of the fiddling in the Arc code is to load/store/iterate over numbered files in the ‘posts’ directory. pyflow includes a simple transactional dict/list store for simple storage (that also happens to work when there’s concurrent users, which I don’t think blog.arc would do right now).

Python functionality that’s somewhat terser than Arc. For example, the main entry point “/blog” looks like this in blog.arc:

(defop blog req
  (let user (get-user req)
      (for i 0 4
        (awhen (posts* (- maxid* i))
          (display-post user it)
          (br 3))))))

vs. in pyflow, it looks like:

def blog(req):
    user = getUser(req)
    return blogPage(flat([displayPost(p, user) for p in reversed(Posts[-5:])]))

List comprehension work nicely here to keep the number of tokens down.

CSS: Some of the layout-y (e.g. (br 3)) code that ends up being code in Arc is in a default.css which also allows for snazzing things up pretty easily. Although, I got lazy so there’s some formatting in the code in pyflow that could be tidied up and shortened.

I don’t mean to imply the blog code couldn’t be squished down more in Arc (I’m sure it could), or that I don’t like Arc’s approach, but I think it’s worth exploring how similar idioms can be expressed in various languages to try to improve everyone’s end solution. Perhaps when Paul releases “news”, I’ll have a chance to see if Arc is more of a win on larger and more production-quality code bases.

Anyhow, I think the discussion surrounding Arc is the best thing that’s happening right now. So, if you’re contributing, keep it up. Lots of interesting stuff coming out. If anyone has any suggestions on how the pyflow example code could be simplified with new idioms, please fire away.