Dsl Examples

Enough handwaving, fluffy posts. How about something concrete?

What do Compojure, Korma, and core.logic have in common? Let’s see.

Compojure

Compojure is a web framework DSL. It lets you specify code to handle various request paths in URI’s. For a full explanation of the API, checkout the docs. I’m just going to focus on the basics.

From my last the post, the first question to ask is what do you want to compose with this DSL. In Compojure’s case, it’s handlers for various URI paths. Individually, these are specified using one of 5 macros; ANY, GET, PUT, POST, DELETE. Each of these has a syntax for specifying parameters included in the request and other niceties.

The second question, is what are the operators used to compose these handlers? The only way that really makes sense is to just merge a collection of handlers together. This is done using the routes function.

Third, what does this DSL hide? It hides all the plumbing to take a Ring request map, find a matching handler and call it.

Fourth, what is it’s ‘run’ function? The site in the compojure.handler namespace, which takes a set of routes and creates a ring handler from it.

To summarize: - Composes: HTTP handlers (ANY, GET, PUT, POST, DELETE) - Operators: routes - Hides: specifics of matching a request to a handler - run function: compojure.handler/site

Example: project.clj -

(defproject test-compojure "1.0.0-SNAPSHOT"
   :description "FIXME: write description"
   :dependencies [[org.clojure/clojure "1.3.0"]
                  [compojure "1.0.1"]] 
   :dev-dependencies [[lein-ring "0.4.5"]] 
   :ring {:handler test-compojure.core/app}) 

src/test-compojure/core -

(ns test-compojure.core
  (use compojure.core)
  (:require [compojure.handler :as handler])) 

(def r (GET "/" []
            "howdy, globe"))

(def s (GET "/sub" []
            "sub route"))

(def ss (GET "/sub/sub" []
             "sub, sub route"))

(def rs (routes r s))

(def app
  (handler/site (routes rs ss)))

To use:

lein new test-compojure
; copy the above files to the appropriate places
lein deps
lein ring server

I like compojure and I use it. But looking at the above, it feels ‘lumpy’ to me. For instance, the return value of GET is different from that of route. (James Reeves set me straight in the comments) Also, if you have a collection of routes that all have a common prefix, you have to use a macro called context. And the syntax to parse out parts of the path and assign them to symbols is clunky. I’ll try to show an alternative in a future post.

Korma

Korma is a very nice DSL by Chris Granger to wrap SQL queries. I kind of think of it as a couple of different DSLs; one to do things with SQL tables and one to query the tables. For the sake of space and your patience, I’m only going to look at the query DSL.

After defining what your tables look like and their inter-relationships using defentity, you query the database using the select macro.

(select users
  (aggregate (count :*) :cnt)
  (where (or (> :visits 20)
             (< :last_login a-year-ago))))

What you’re composing here are the parts of the query; the aggregate and where expressions. There are other query parts that can be composed to create a full query. Check out the docs for the complete list.

However, if you want to define the query parts seperately, you have to do something like:

(def base (-> (select* "user")
            (fields :id :username)
            (where {:email [like "*@gmail.com"]})))

(def ordered-and-active (-> base
                          (where {:active true})
                          (order :created)))

(def constrained (-> ordered-and-active
                   (limit 20)))

(exec constrained)

I find Korma really useful and it’s saved me a bunch of time. But it also feels lumpy to me for the same reasons Compojure does.

core.logic

David Nolen implemented the miniKanren logic programming language described in the book “The Reasoned Schemer” and did a great job. There’s a ton of good stuff to learn reading through the source and the book.

core.logic is all about relational programming, which is totally different from relational algebra. Looked at as a DSL, it lets you compose relations, which are created using the unify operator ==. (Check out Ambrose’s intro to core.logic for explanations.)

There are a handful of operators to compose relations: fresh and all for sequential composition, conde for parallel composition, lcons which constructs sequences to be used in relations

Iteration is accomplished using recursion.

Theres a lot more to core.logic, but thats the enough for our purposes. That handful of operators will let you accomplish a surprising amount. When you write code using core.logic, what you don’t see are the substitutions. You can think of substitutions as collections of relations at a single point in the process of evaluation. As a set of relations are evaluated, substitutions are built up. A conde expression is evaluated, multiple substitutions might be created as alternatives. You can picture the process as a tree with each branch being an alternative. If a new relation is attempted to be added to a substitution that would violate a previous relation, that branch is marked as failed and the process backtracks to a previous branch point to try another alternative. The set of answers is produced from the set of substitutions that didn’t fail. And if all that sounds complicated, it is. core.logic hides it all from you.

To run a core.logic program, you use the run macro and its variants.

core.logic feels very uniform to me, which is a sign of its refinement. Primitive relations are created with == and the operators accept relations as parameters and produce relations which serve as inputs to operators. You can create functions that return relations using generic Clojure defn expressions and you can assign relations to Clojure symbols using def.

Fini

This has been a whirlwind look at 3 DSLs in Clojure without a lot of detail. Next time, I plan on showing the development of a simple DSL.

Jim Duey 22 February 2012
blog comments powered by Disqus