Basic Concepts

Setting Up an IR

The only step required before we can start manipulating programs in Squid is to instantiate an intermediate representation (IR) to manipulate the programs in. Different configurable IRs are provided by Squid with different properties, pros, and cons that you should consider in light of your intended use cases.

The choice of an IR impacts the way programs are internally represented, and thus affects the semantics of some operations performed on code fragments, such as pattern-matching and transformation. More on that is explained in the Intermediate Representation reference; for now, we will just use the simplest IR there is: a simple abstract syntax tree (AST). To instantiate this IR, proceed as follows:

object IR extends squid.ir.SimpleAST
import IR.Predef._

Importing IR.Predef._ brings into scope all the definitions we will need in order to manipulate code in a type-safe way. Note that it is important to not import the whole content of the IR with import IR._, as this would import many more definitions not useful to us.

Types of Programs

As part of an IR’s Predef, a few important types are defined. They are summarized in the table below:

Type Equivalent to Description
Code[T,C]   code fragments of type T and context C
ClosedCode[T] Code[T,Any] closed code fragments
OpenCode[T] Code[T,Nothing] open code fragments
CodeType[T]   program type representations
Variable[T]   variable symbols


Note that the Code[+Typ,-Ctx] class is covariant in Typ and contravariant in Ctx, so for any T, C and D, we have the subtyping relations ClosedCode[T] <: Code[T,C] <: Code[T, C & D] <: OpenCode[T] where C & D is the intersection of types C and D, equivalent to C with D in Scala. These relations are explained and justified later in the Squid tutorial, but intuitively a closed code fragment is one that can be used in any context (C = Any), while an open code fragment is one that no context (C = Nothing) can be statically guaranteed to satisfy. The & type operator is defined in squid.utils, so to use it you will have to import squid.utils._ (or, more selectively import squid.utils.&).

Quasiquotation and Code Composition

A quasiquote is a quote in which some “holes” are left to be filled in by arbitrary values. The most well-known example of quasiquotation in Scala is interpolated string literals, of syntax s"..." in which $ is used to denote holes. For example:

scala> val a = s"2"
a: String = 2

scala> s"($a + $a) * 2"  // inserts 'a' into two quasiquote holes
res0: String = (2 + 2) * 2

The primary way of manipulating programs in Squid is to use code quasiquotes, which are like interpolated string literals, except that instead of representing a sequence of characters, they represent fragments of programs that are internally encoded in an intermediate representation (IR).

To use quasiquotes, do not forget to import your IR.Predef._, as explained above. The syntax is code"...", as shown below:

scala> val a = code"2"
a: IR.ClosedCode[Int] = code"2"

scala> code"($a + $a) * 2"
res1: IR.ClosedCode[Int] = code"(2).+(2).*(2)"

Looking at the types in the REPL session above, we can see that we are manipulating closed terms of type Int. We also notice that the textual representation of the printed term is different from the one we used in the original quasiquote, because the term is internally represented in a normalized form by our IR, where any information about parentheses and operator-syntax is lost.

The Const function can be used to lift normal values to terms (code values):

scala> Const(42)
res2: IR.ClosedCode[Int] = code"42"

scala> List(1,2,3).map(n => code"${Const(n)}.toDouble")
res3: List[IR.ClosedCode[Double]] = List(code"1.toDouble", code"2.toDouble", code"3.toDouble")

Quasi​code Syntax Alternative

The syntax code{ ... $(x)... }, referred to as quasicode, can be used in place of code" ... ${x}... " in expression position (but not in case patterns). This has several advantages, including good IDE integration – autocompletion, syntax and error highlighting, jump-to-definition, etc.

In order to use this syntax, import IR.Quasicodes._ first. For example:

scala> import IR.Quasicodes._
import IR.Quasicodes._

scala> val a = code{2}
a: IR.ClosedCode[Int] = code"2"

scala> code{($(a) + $(a)) * 2}
res4: IR.ClosedCode[Int] = code"(2).+(2).*(2)"