You can see what types auto supports "out-of-the-box" by browsing the
instances for the Interpret class. For example, the following instance
says that we can directly decode any Dhall expression that evaluates to a
Bool into a Haskell Bool:

instance Interpret Bool

... which is why we could directly decode the string "True" into the
value True.

There is also another instance that says that if we can decode a value of
type a, then we can also decode a List of values as a Vector of as:

instance Interpret a => Interpret (Vector a)

Therefore, since we can decode a Bool, we must also be able to decode a
List of Bools, like this:

>>> input auto "[True, False]" :: IO (Vector Bool)
[True,False]

We could also specify what type to decode by providing an explicit Type
instead of using auto with a type annotation:

>>> input (vector bool) "[True, False]"
[True, False]

Exercise: Create a ./config file that the following program can decode:

Types

The interpreter complains because the string "1" cannot be decoded into a
Haskell value of type Bool.

The code excerpt from the above error message has two components:

the expression being type checked (i.e. 1)

the expression's expected type (i.e. Bool)

Expression
⇩
1 : Bool
⇧
Expected type

The (:) symbol is how Dhall annotates values with their expected types.
This notation is equivalent to type annotations in Haskell using the (::)
symbol. Whenever you see:

x : t

... you should read that as "we expect the expression x to have type
t". However, we might be wrong and if our expected type does not match the
expression's actual type then the type checker will complain.

In this case, the expression 1 does not have type Bool so type checking
fails with an exception.

Exercise: Load the Dhall library into ghci and run these commands to get
get a more detailed error message:

Each file path is replaced with the Dhall expression contained within that
file. If that file contains references to other files then those references
are transitively resolved.

In other words: configuration files can reference other configuration files,
either by their relative or absolute paths. This means that we can split a
configuration file into multiple files, like this:

$ cat > ./config <<EOF
{ foo = 1
, bar = ./bar
}
EOF

$ echo "[3.0, 4.0, 5.0]" > ./bar

$ ./example
Example {foo = 1, bar = [3.0,4.0,5.0]}

However, the Dhall language will forbid cycles in these file references. For
example, if we create the following cycle:

This is because the parser thinks that ./baz: is a single token due to
the missing whitespace before the colon and tries to import a file named
./baz:, which does not exist. To fix the problem we have to add a space
after ./baz:

Lists

You can store 0 or more values of the same type in a list, like this:

[1, 2, 3]

Every list can be followed by the type of the list. The type annotation is
required for empty lists but optional for non-empty lists. You will get a
type error if you provide an empty list without a type annotation:

You cannot omit the type annotation for Optional values. The type
annotation is mandatory

Exercise: What is the shortest possible ./config file that you can decode
like this:

>>> input auto "./config" :: IO (Maybe (Maybe (Maybe Integer)))
???

Exercise: Try to decode an Optional value with more than one element and
see what happens

Records

Record literals are delimited by curly braces and their fields are separated
by commas. For example, this is a valid record literal:

{ foo = True
, bar = 2
, baz = 4.2
}

A record type is like a record literal except instead of specifying each
field's value we specify each field's type. For example, the preceding
record literal has the following record type:

{ foo : Bool
, bar : Integer
, baz : Double
}

If you want to specify an empty record literal, you must use {=}, which is
special syntax reserved for empty records. If you want to specify the empty
record type, then you use {}. If you forget which is which you can always
ask the dhall compiler to remind you of the type for each one:

$ dhall
{=}
<Ctrl-D>
{}
{=}

$ dhall
{}
<Ctrl-D>
Type
{}

You can access a field of a record using the following syntax:

record.fieldName

... which means to access the value of the field named fieldName from the
record. For example:

However, you can't convert anything more complex than that like a polymorphic
or higher-order function). You will need to apply those functions to their
arguments within Dhall before converting their result to a Haskell value.

Compiler

We can also test our makeBools function directly from the command line.
This library comes with a command-line executable program named dhall that
you can use to both type-check files and convert them to a normal form. Our
compiler takes a program on standard input and then prints the program's type
to standard error followed by the program's normal form to standard output:

These "double single quote strings" ignore all special characters, with one
exception: if you want to include a '' in the string, you will need to
escape it with a preceding ' (i.e. use ''' to insert '' into the final
string).

These strings also strip leading whitespace using the same rules as Nix.
Specifically: "it strips from each line a number of spaces equal to the
minimal indentation of the string as a whole (disregarding the indentation
of empty lines)."

You can also interpolate expressions into strings using ${...} syntax. For
example:

$ dhall
let name = "John Doe"
in let age = 21
in "My name is ${name} and my age is ${Integer/show age}"
<Ctrl-D>
Text
"My name is John Doe and my age is 21"

Note that you can only interpolate expressions of type Text

If you need to insert a "${" into a string without interpolation then use
"''${" (same as Nix)

''
for file in *; do
echo "Found ''${file}"
done
''

Combine

You can combine two records, using either the (//) operator or the
(/\) operator.

The (//) operator (or (⫽) U+2AFD) combines the fields of both records,
preferring fields from the right record if they share fields in common:

Note that the order of record fields does not matter. The compiler
automatically sorts the fields.

The (/\) operator (or (∧) U+2227) also lets you combine records, but
behaves differently if the records share fields in common. The operator
combines shared fields recursively if they are both records:

Unions

A union is a value that can be one of many alternative types of values. For
example, the following union type:

< Left : Natural | Right : Bool >

... represents a value that can be either a Natural or a Bool value. If
you are familiar with Haskell these are exactly analogous to Haskell's
"sum types".

Each alternative is associated with a tag that distinguishes that alternative
from other alternatives. In the above example, the Left tag is used for
the Natural alternative and the Right tag is used for the Bool
alternative.

A union literal specifies the value of one alternative and the types of the
remaining alternatives. For example, both of the following union literals
have the same type, which is the above union type:

< Left = +0 | Right : Bool >

< Right = True | Left : Natural >

You can consume a union using the built-in merge function. For example,
suppose we want to convert our union to a Bool but we want to behave
differently depending on whether or not the union is a Natural wrapped in
the Left alternative or a Bool wrapped in the Right alternative. We
would write:

Exercise: Create a list of the following type with at least one element
per alternative:

List < Left : Integer | Right : Double >

Polymorphic functions

The Dhall language supports defining polymorphic functions (a.k.a.
"generic" functions) that work on more than one type of value. However,
Dhall differs from Haskell by not inferring the types of these polymorphic
functions. Instead, you must be explicit about what type of value the
function is specialized to.

Take, for example, Haskell's identity function named id:

id :: a -> a
id = \x -> x

The identity function is polymorphic, meaning that id works on values of
different types:

>>> id 4
4
>>> id True
True

The equivalent function in Dhall is:

λ(a : Type) → λ(x : a) → x

Notice how this function takes two arguments instead of one. The first
argument is the type of the second argument.

Let's illustrate how this works by actually using the above function:

$ echo "λ(a : Type) → λ(x : a) → x" > id

If we supply the function alone to the compiler we get the inferred type as
the first line:

You can read the type (∀(a : Type) → ∀(x : a) → a) as saying: "This is the
type of a function whose first argument is named a and is a Type. The
second argument is named x and has type a (i.e. the value of the first
argument). The result also has type a."

This means that the type of the second argument changes depending on what
type we provide for the first argument. When we apply ./id to Integer, we
create a function that expects an Integer argument:

Total

Dhall is a total programming language, which means that Dhall is not
Turing-complete and evaluation of every Dhall program is guaranteed to
eventually halt. There is no upper bound on how long the program might take
to evaluate, but the program is guaranteed to terminate in a finite amount of
time and not hang forever.

This guarantees that all Dhall programs can be safely reduced to a normal
form where as many functions have been evaluated as possible. In fact, Dhall
expressions can be evaluated even if all function arguments haven't been fully
applied. For example, the following program is an anonymous function:

... and even though the function is still missing the first argument named
n the compiler is smart enough to evaluate the body of the anonymous
function ahead of time before the function has even been invoked.

We can use the map function from the Prelude to illustrate an even more
complex example:

Exercise: If you have a lot of spare time, try to "break the compiler" by
finding an input expression that crashes or loops forever (and file a bug
report if you succeed)

Headers

Sometimes you would like to provide additional request headers when importing
Dhall expressions from URLs. For example, you might want to provide an
authorization header or specify the expected content type.

Dhall URL imports let you add or modify request headers with the using
keyword:

https://example.com using ./headers

... where you can replace ./headers with any import that points to a Dhall
expression of the following type:

List { header : Text, value : Text }

For example, if you needed to specify the content type correctly in order to
download the file, then your ./headers file might look like this:

$ cat headers
[ { header = "Accept", value = "application/dhall" } ]

... or if you needed to provide an authorization token to access a private
GitHub repository, then your headers could look like this:

Dhall will forward imports if you import an expression from a URL that
contains a relative import. For example, if you import an expression like
this:

http://example.com using ./headers

... and http://example.com contains a relative import of ./foo then
Dhall will import http://example.com/foo using the same ./headers file.

Import integrity

Sometimes you want to use share code while still ensuring that the imported
value never changes and can't be corrupted by a malicious attacker. Dhall
provides built-in support for hashing imported values to verify that their
value never changes

The first file named ./foo contains an example of an integrity check. You
can add sha256:XXX after any import (such as after ./bar), where XXX is
an expected 64-character sha256 hash of the Dhall value. To be precise,
the hash represents a sha256 hash of the UTF-8 encoding of a canonical text
representation of the fully resolved and normalized abstract syntax tree of
the imported expression.

Dhall will verify that the expected hash matches the actual hash of the
imported Dhall value and reject the import if there is a hash mismatch:

$ dhall <<< './foo'
Integer
1

This implies that the hash only changes if the Dhall value changes. For
example, if you add a comment to the ./bar file:

$ cat ./bar
-- This comment does not change the hash
./baz

... then ./foo will still successfully import ./bar because the hash
only depends on the normalized value and does not depend on meaningless
changes to whitespace or comments:

$ dhall <<< './foo' # This still succeeds
Integer
1

You can compute the Hash for any import by using the dhall-hash utility
installed by this package. For example:

This is because the ./bar file now represents a new value (2 instead of
1), even though the text of the ./bar is still the same. Since the value
changed the hash must change as well. However, we could change ./baz to:

$ cat baz
if True then 1 else 2

... and the import would succeed again because the final result is still 1.

The integrity hash ensures that your import's final meaning can never change,
so an attacker can never compromise an imported value protected by a hash
unless they can break SHA-256 encryption. The hash not only protects the
file that you immediately import, but also protects every transitive import
as well.

You can also safely refactor your imported dependencies knowing that the
refactor will not change your hash so long as your refactor is
behavior-preserving. This provides an easy way to detect refactoring errors
that you might accidentally introduce. The hash not only protects you
from attackers, but also protects against human error, too!

Raw text

Sometimes you want to import the contents of a raw text file as a Dhall
value of type Text. For example, one of the fields of a record might be
the contents of a software license:

Normally if you wanted to import a text file you would need to wrap the
contents of the file in double single-quotes, like this:

$ cat LICENSE
''
Copyright (c) 2017 Gabriel Gonzalez
All rights reserved.
...
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''

... which does not work well if you need to reuse the same text file for
other programs

However, Dhall supports importing a raw text file by adding as Text to the
end of the import, like this:

$ cat LICENSE
Copyright (c) 2017 Gabriel Gonzalez
All rights reserved.
...
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Formatting code

This package also provides a dhall-format executable that you can use to
automatically format Dhall expressions. For example, we can take the
following unformatted Dhall expression:

Carefully note that the code formatter does not preserve all comments.
Currently, the formatter only preserves leading comments and whitespace
up until the last newline preceding the code. In other words:

$ dhall-format
{- This comment will be preserved by the formatter -}
-- ... and this comment will be preserved, too
{- This comment will *NOT* be preserved -} 1
-- ... and this comment will also *NOT* be preserved
<Ctrl-D>
{- This comment will be preserved by the formatter -}
-- ... and this comment will be preserved, too
1

Built-in functions

Dhall is a restricted programming language that only supports simple built-in
functions and operators. If you want to do anything fancier you will need to
load your data into Haskell for further processing

This section covers types, functions, and operators that are built into the
language, meaning that you do not need to import any code to use them.
Additionally, Dhall also comes with a Prelude (covered in the next section)
hosted online that contains functions derived from these base utilities. The
Prelude also re-exports all built-in functions for people who prefer
consistency.

The following documentation on built-ins is provided primarily as a reference.
You don't need to read about every single built-in and you may want to skip to
the following Prelude section.

Caveats

Dhall differs in a few important ways from other programming languages, so
you should keep the following caveats in mind:

First, Dhall only supports addition and multiplication on Natural numbers
(i.e. non-negative integers), which are not the same type of number as
Integers (which can be negative). A Natural number is a number prefixed
with the + symbol. If you try to add or multiply two Integers (without
the + prefix) you will get a type error:

In fact, there are no built-in functions for Integers (or Doubles). As
far as the language is concerned they are opaque values that can only be
shuffled around but not used in any meaningful way until they have been
loaded into Haskell.

Second, the equality (==) and inequality (!=) operators only work on
Bools. You cannot test any other types of values for equality.

Overview

Each of the following sections provides an overview of builtin functions and
operators for each type. For each function you get:

An example use of that function

A "type judgement" explaining when that function or operator is well
typed

Prelude

There is nothing "official" or "standard" about this Prelude other than
the fact that it is mentioned in this tutorial. The "Prelude" is just a
set of convenient utilities which didn't quite make the cut to be built into
the language. Feel free to host your own custom Prelude if you want!

If you visit the above link you can browse the Prelude, which has a few
subdirectories. For example, the Bool subdirectory has a not file
located here:

You can also request features, support, or documentation improvements on the
above issue tracker.

If you would like to contribute to the Dhall project you can try to port Dhall
to other languages besides Haskell so that Dhall configuration files can be
read into those languages, too.

Frequently Asked Questions (FAQ)

Why do empty lists require a type annotation?

Unlike Haskell, Dhall cannot infer a polymorphic type for the empty list
because Dhall represents polymorphic values as functions of types, like this:

λ(a : Type) → [] : List a

If the compiler treated an empty list literal as syntactic short-hand for
the above polymorphic function then you'd get the unexpected behavior where
a list literal is a function if the list has 0 elements but not a function
otherwise.

Does Dhall support type synonyms like Haskell?

No. For example, the following expression will not type-check:

let MyType = Integer in 1 : MyType

Haskell can support type synonyms because Haskell does not allow type-level
functions like Dhall does. Dhall's support for type-level computation means
that type synonyms cannot be safely substituted until after the type-checking
phase, otherwise type-checking might infinitely loop

You can work around this limitation using Dhall's import system by saving the
type synonym to a path and importing that path, like this:

cat ./MyType
Integer

1 : ./MyType -- This will type-check

This is because import resolution precedes type-checking and does not run the
risk of causing the type-checker to diverge