Web DSL's

December 1, 2015

Why DSL's can be better than the real thing.

It’s always nice to see well-written, considered, and non-inflammatory writing on the static vs dynamic typing debate, and this post by Elben Shira titled “The End Of Dynamic Languages” is definitely in that category.

I’m not actually going to talk much about static vs dynamic typing here, except to say this: as someone who came from many years of working with static languages (C, C++, Java) and switched allegiance to a dynamic language,1 I don’t think there’s a yes/no answer to which is “best”. One delivers benefits by catching logic errors early, while restricting programs to those that the developer can prove to be correct within its type system. The other enables free expression and experimentation, at the cost of allowing type errors to manifest at runtime, and putting more onus on the developer to keep their logic maintainable.

Put it this way: I’d use one for controlling a nuclear reactor, and the other for developing a web application — you can probably guess which.

For myself, I often find constraints of even the best static type systems overly constrain my thinking, I and feel they often don’t pay their way. The author is certainly right that with Clojure and other dynamic languages it’s easy to make the mistake of passing ill-defined “maps of things” around, making maintenance of long-lived software painful (been there). However, so-called “gradual typing” approaches such as Prismatic Schema (also been there, and can recommend) mitigate this a great deal. The fact that Clojure even allows these sort of core capabilities to be added so elegantly is a big point in its favour.

Domain-Specific Language

Anyway, that’s as far as I wade into the debate on static vs. dynamic. What actually triggered this post was something Elben said while talking about using Clojure as a DSL:

Hiccup, for example, generates HTML. So instead of writing:

<span class="foo">bar</span>

You write:

[:span {:class "foo"} "bar"]

But HTML is the perfect DSL for writing HTML — why replace it for another DSL with your own set of rules and restrictions, and lose the decades of tooling and know-how of every designer on the face of the planet?”

Now, notwithstanding that you would usually write the more pithy [:span.foo "bar"] in Hiccup, this misses the main point of expressing HTML this way: Clojure is a programming language, HTML is not. The DSL is Clojure data, to be manipulated and generated with all the power-tools available for doing so in Clojure. This example from a previous post shows why this is valuable (see the post for an explanation of what this is):

(defn archives-page [articles]
  (base-page "Sapient Pair - Blog Archive"
    [:div#archives.with-margin
     [:h1.title "Blog Archives"]
     (for [[year articles] (->> articles
                             (group-by (comp year :published))
                             (sort descending))]
       [:div.year-articles
        [:h1.year year]
        (for [article (sort-by :published descending articles)]
          [:div.article.row
           [:h2.article-title
            [:a {:href (str "../" (:path article))}
             (h (:title article))]]
           [:div.article-date
            (format-date "MMM dd" (:published article))]
           (if (:tags article)
             [:div.tags "Posted in: "
              (->>
                (for [tag (sort (:tags article))]
                  [:span.tag (h tag)])
                (interpose ", "))])])])]))

The above is about embedding HTML in code (code drives the HTML). When you want the opposite, code embedded in HTML (HTML drives the code) — which would be the case if someone is producing the content in an web development toolchain — then you would use something like Enlive, which Elben mentions. The context will determine which approach makes most sense, both can be mixed and matched as needed.

CSS

Another example where DSL’s are great came up recently, this time for CSS.

Having written a lot of CSS directly in the past, I was aware of some of its pitfalls. Off the top of my head, two of the more annoying ones are:

  • When writing rules that target nested classes, e.g. .column .heading to style column headings, one has to start a new rule for every “sub rule”, You end up with long strings of rules like .column .heading, .column .date .column .content, etc when you’d rather just nest those in a root .column rule.

  • Most stylesheets have a set of implicit constants, such as theme colours, fonts, border radiuses, etc. inlined into the CSS. When you decide to change any of these you need to hackety-hack search and replace, and hope you remember to also change any related things, such as derived colours (e.g. lighter versions of the primaries).

These issues are why DSL’s such as SASS and LESS are popular: they make maintaining complex CSS less error-prone by making it more expressive.

Garden is a DSL in the same tradition, except — like Hiccup — it also leverages a powerful programming language.

Gardening

As it happens, being able to program CSS is exactly what I needed recently. My goal was to produce self-contained HTML reports with no external URL resources to be the content of an email message. But the same report system would later be used in a more general context where it would make more sense to load resources via HTTP URL’s.

Garden allowed me to create CSS for the reports that can either contain the resources needed by the web page (in this case the SVG images used for icons) as inline data: URI’s, or http: URL’s.

In the example below, form-css is a function that generates Garden CSS. It takes a resource-to-uri function parameter which, given a resource name (such as an SVG image) generates a URL suitable for use in CSS. We can pass the resource-to-data-uri function as this parameter and we’ll get back CSS with URI’s containing the data inlined in BASE64 format. A different implementation will be used to generate URL’s pointing to an assets area on the server.

(defn form-css [resource-to-uri]
  (let [impact-colour (colour/hex->rgb "0E76EB")
        impact-radius (px 8)
        impact-padding (px 5)]
    [[:.root-container {:margin "0 auto"
                        :max-width "800px"
                        :min-width "200px"
                        :width "100%"
                        :background-color "white"}]

     [...]

     ;; forms
     [:.element {:margin "20 0 20 0"}]

     [:.label {:margin "10 5 10 0"}]

     [:.inline {:display "inline-block"}]

     [:.checkbox {:display "inline-block"
                  :width "1.2em"
                  :height "1.2em"}]
     [:.checked {:background
                 (image-background-url resource-to-uri "tick-circle.svg")
                 :background-size "cover"}]
     [:.unchecked {:background
                   (image-background-url resource-to-uri "empty-circle.svg")
                   :background-size "cover"}]

     [...]

     ]))

(defn resource-to-data-uri [resource]
  (format "data:%s;base64,%s"
    (mime-type resource)
    (-> (base64/encode (.getBytes (slurp resource) "utf-8"))
        (bytes)
        (String. "ascii"))))

(defn icon-path [icon-name]
  (str "web/icons/forms/" icon-name))

(defn mime-type [^String resource-name]
  (case (.substring resource-name (- (.length resource-name) 4))
    ".svg"
    "image/svg+xml"))

(defn image-background-url [resource-to-uri image-name]
  (-> (css-url (resource-to-uri (icon-path image-name)))
      (str "center center no-repeat")))

(defn css-url [value]
  (str "url('" value "')"))

As well as allowing me to solve the problem of embedding resources, and giving me a way to avoid my pet CSS peeves mentioned above, Garden also allowed me to add some nice abstractions, such as inlining the “clearfix” method, which is a way of coaxing an HTML element to fully contain its floating children.2 It’s purely aesthetic, but I like it that the code generating the markup doesn’t need to know about the clearfix hackage.

[...]

    (clear-both
      [:.container {:clear "both"}
       [[:.column {:width "33%"
                   :max-width 250
                   :margin-top 10}]
        [:.first {:float "left"
                  :clear "both"}]
        [:.next {:float "left"}]]
       [:.label {:display "inline"
                 :margin-right 10}]])

[...]

(defn clear-both [css]
  (conj css
    [:&:before :&:after {:content "\" \""
                         :display "table"}]
    [:&:after {:clear "both"}]))
  1. While briefly going hard in the opposite direction via Haskell, which some might say is the current ultimate in static typing. ↩︎

  2. HTML elements can be made to “float”, which takes them out of consideration of the layout of their non-floating siblings and allows them to float to the left or right of the parent’s region. But, because they’re not laid out as child elements, they also aren’t included in the total area computed for the parent: the parent will not expand to encompass its floating children. This is rarely what you want, and the clearfix “hack” is a pure-CSS way to change this. ↩︎