Skip to content

Tutorial: Introduction

adriaanm edited this page Oct 10, 2011 · 2 revisions

The embedding of domain specific languages in Scala is based on a simple principle: the domain program looks like it's written in its own language with its own syntax, but the domain program is actually just a plain Scala program, where method calls are given a special meaning. Applying this principle naively, you end up with a "shallow embedding": DSL programs are just thinly veiled Scala programs. We refine this so that DSL programs can be analyzed (and thus optimized) by the DSL implementation. The latter feature is usually only offered by a "deep embedding", which typically has a higher implementation cost. We aim to shallowly embed our domain programs and optimize them too.

Example: Parser Combinators

(If you're not familiar with how parser combinators work, you may skip this section. It does not introduce anything new. It simply aims to ground the abstract concepts explained above in a concrete example that exists outside virtualized Scala.)

A similar principle is used in the parser combinator library: you write a | b to express the alternative of the production a or b, which looks like BNF, but it's also a valid Scala expression. More precisely, it's syntactic sugar for the method call a.|(b), where | is a method defined in the Parser class. The | method call does not do any parsing: it simply builds the description of a parser, which can later be applied to input.

We say that the domain program (a | b), which looks like it's written in a domain-specific language (BNF) with its own syntax, is represented by method calls in the host language, and these method calls are "intercepted" by the DSL to construct its internal representation of the domain program, or possibly to execute it directly, depending on the chosen implementation strategy.