Dhall support in depth

By @lucasdicioccio, 811 words, 8 code snippets, 5 links, 0images.

this page uses a default layout

The Dhall support in KitchenSink provides a template engine unlike many static-site generators. In typical static-site generators, the template language serves two purposes: dictating the structure of the HTML files, and generating repetitive content of HTML files like for data tables. In KitchenSink, Layouts dictate the HTML structure and are hard to change. Dhall provides a pre-processor to apply some template on repetititve contents to render Sections.

Dhall as a pre-processor

Why Dhall rather than other languages? The short answer is that Dhall hits a sweet spot for pre-processing sections in static-blog generators. Dhall is at a same time: deterministic, easy to embed in Kitchen-Sink, and expressive. Let’s develop these points.

First, Dhall is a deterministic language. A major benefit here is that Dhall is simple on a conceptual level: you can substitute a Dhall expression with its result and it will work as expected. Further, being deterministic, Dhall disallows user-defined side-effects. Albeit a constraint for people writing Dhall code, such determinism comes handy to write a pre-processor: a pre-processor turns Sections into Sections, since the pre-processor does not do more, for instance, by having no extra side-effects to track. We do not freeze KitchenSink development into a bad-architecture as the Section -> Section function can be moved without surprises.

Second, Dhall is embeddable in Haskell, the programming language in which KitchenSink is written. The immediate benefit is that no external setup is required: KitchenSink embeds the same Dhall interpreter as the official Dhall binary and that’s it. Compare this simplicity (no setup required) to generator sections, whose result vary depending on which tools are available on each setups.

Third, Dhall is expressive enough to have libraries of functions that you import over the network or on the file-system. Such imports open the door to avoiding redundant code (e.g., to re-use the same base:social content in every article). Networked imports open the door to community-based sharing.

Summarizing, the choice of Dhall is not that surprising after analyzis. Dhall fits the problem of pre-processing article sections pretty well, without costing much to implement and without freezing the design of KitchenSink engine.

Examples

Let’s now give some examples.

Rendering a section in CommonMark

example 1

=base:main-content.dhalL

{ contents = ["__generated from dhall__", "\n", kitchensink.file ], format = "cmark" }

example 2

=base:main-content.dhall
    
{ contents = [
    "::: output"
  , "\n"
  , "__this code block is defined in some Dhall__"
  , "\n"
  , "file=", kitchensink.file
  , "\n", "section="
  , Integer/show kitchensink.sectionNum
  , "\n"
  , ":::"
  ]
, format = "cmark"
}

rendered in HTML (via CommonMark)

this code block is defined in some Dhall

file= website-src/sections-dhall.cmark

section= +9

some number from a dataset: 42

some list from a dataset:

  • foo
  • bar
  • baz

Rendering a section in HTML

You can return HTML directly-formatted.

example

=base:main-content.dhall

{ contents = ["__generated from dhall__", "\n", kitchensink.file ], format = "html" }

Rendering JSON-sections

Dhall can substitute itself to JSON objects as well. Jut replace a .json with .dhall and KitchenSink will interpret a Dhall expression to fill-in the JSON.

A few use cases are envisioned:

  • generating a commands from Dhall expressions
  • generating a command requiring the filename
  • avoiding redundant information like social infos using Dhall’s support for remote includes

example 1

=generator:cmd.dhall
let
  ping = { cmd = "ping"
         , args = ["-c", "3", "8.8.4.4"]
         , target = "latency"
         }
in
{ contents = ping
, format = "json"
}

example 2

This section is handy to show the content in this file. Note that I use kitchensink.file to get the path name.

=generator:cmd.dhall
let
  cmd = { cmd = "cat"
        , args = [kitchensink.file]
        , target = "cat-this-file"
        }
in
{ contents = cmd
, format = "json"
}

once the generated section is evaluated

example 3

You could write the content in ./social.dhall once and import the content.

=base:social.dhall
{ contents = ./social.dhall
, format = "json"
}

example 4

You can also generate JSON datasets if for some reason writing them in JSON is not immediate enough .

=base:dataset.dhall alice-bob
{ contents =
    [ {name = "Alice", posts = 22.0}
    , {name = "Bob", posts = 7.0}
    ]
, format = "json"
}

rendered

Implementation notes

The following notes are more useful for myself and for people curious about modifying or contributing to KitchenSink.

present

The evaluation chain

Today SiteLoader traverses all sections (i.e., after mapping Article Sections to Article Sections). Upon encountering a .dhall section, SiteLoader evaluates the content as a Dhall expression returning some well-known format.

Further, datasets encountered during this phase are collected in a datasets object. Previous datasets are passed to Dhall sections. Thus Dhall can templatize an in-line dataset.

Thus, unlike generators, Dhall sections are pre-processors. They are evaluated once. When operating Kitchen-Sink as a static-site generator there is no much difference, however if you operate Kitchen-Sink as a live-website, then you cannot use Dhall as a generator-on-steroids.

The kitchensink object

Evaluated Dhall expressions carry information provided by KitchenSink. This information is bound to the kitchensink Dhall value and has the following structure:

{ file : Text -- contains the source file path for the cmark
, sectionNum : Integer -- contains the section number of this file (starting from zero)
, datasets : <data-dependant> -- contains a record<dataset-name, json-value-to-dhall> or an error message if it failed to load
}

future work

  • carry-over more context from previous sections
  • allow appending new sections (i.e., preprocess articles over sections)?