1. 17

  2. 5

    Let’s explore part of E which isn’t covered in the post. E has a feature where users can define custom DSLs with quasi-quotation. A top-level user makes a statement like:

    e`def x := $y`

    Where “def” is an E keyword and the overall string codes for an E expression, but where “$y” refers to a value in the surrounding scope. This might expand, in a kernel language, to:

    ::"e``".valueMaker(["def x := ", ::"e``".valueHole(0)]).substitute([y])

    Here the square brackets denote lists, and the colons are a sort of shame-syntax which identifies the object “e``” without further nested quotation. This object, called a quasi-quoter, has the responsibilities of parsing some string of syntax, allocating holes for values, and substituting those values into holes. This complex-looking scheme is in fact the simplest possible which can support another behavior:

    def expr := e`def x := 42`
    def e`def x := @y` := expr

    Here, the user is first building a code literal, and then defining the name “y” in terms of part of it. Like in Perl, the “@” symbol indicates that the pattern gives a name to part of what it is matching. There is a kernel expansion, with pattern holes, but it is not as readable as the above expansion:

    def via (_quasiMatcher.run(::"e``".matchMaker(["def x := ", ::"e``".patternHole(0)]), [])) [y] := expr

    This API is powerful enough to allow values and patterns to occur simultaneously. However, the elephant in the room is that this feature is simply not used much. I have written a fair amount of Monte, a modern flavor of E whose kernel language I’ve been using for these demonstration expansions, and we only have a few quasi-quoters:

    • “``” does basic string interpolation, like in Perl, Ruby, or JS
    • “b``” does bytestring interpolation, somewhat like Python b''
    • “m``” builds Monte code literals

    These are very useful when doing string manipulation, networking, and even part of the Monte-in-Monte compiler. But that’s really about it. For example, I followed the idea of Data-E tried building versions for XML and JSON, but they simply weren’t compelling compared to an applicative DSL and native Monte data structures, respectively. For example, while I experimented with something like:

    html`<p>An <em>appealing</em> DSL</p>`

    Ultimately it was not any more pleasant or shorter than:

    tag.p("An ", tag.em("appealing"), " DSL")

    And this scales up to full applications. I have recently been writing a raytracer, and its core data structure is built declaratively from an applicative DSL. The DSL’s written in Zephyr ASDL, and the parser’s fully written in Monte, with a compiler into Monte. Surely this should be a good situation for quasi-quotation? No, not really! There aren’t any holes or substitutions. There’s just straightforward applications of the Interpreter Pattern over and over. For example, here is the universal katamorphism, or fold, for copying one of these declarative trees from one process to another over the network, based on this portion of my raytracer:

    def copyASDLTo(ref) as DeepFrozen:
        return object copier:
            match [verb, args, namedArgs]:
                M.send(ref, verb, args, namedArgs)

    And it would be used just by declaring the tree and calling the tree with the katamorphism:

    def expr := myDSL.MyNode(…)
    when (def remoteExpr := expr(copyASDLTo(remoteMachine)) ->
        traceln(`Sent $expr to $remoteMachine successfully, building $remoteExpr`)
    1. 3

      In my twenties, I used to do a lot of programming in Tcl. A language that most would agree is about as far removed from a modern statically typed programming language as you can get. (Often pointed out with something close to utter disgust). And yet the idea of making illegal states unrepresentable was entirely natural to myself and other Tcl programmers. I would frequently start a programming project by creating a blank-slate interpreter, stripping out all the built-in language constructs (loops, procedures, even arithmetic), and then adding back in a carefully selected set of high-level domain-specific primitives. This DSL would then become the application’s configuration file, safe in the knowledge that illegal configurations cannot be expressed.

      TCL had/has such depth often lurking around in corners. One of my favorite languages of all time!