Dhall is a programmable configuration language that provides a non-repetitive alternative to YAML.
You can think of Dhall as: JSON + functions + types + imports
Note that while Dhall is programmable, Dhall is not Turing-complete. Many of Dhall’s features take advantage of this restriction to provide stronger safety guarantees and more powerful tooling.
1-- Single-line comment
2
3{- Multi-line comment
4
5 Unicode is fine 🙂
6
7 This file is a valid Dhall expression that evaluates to a large record
8 collecting the results of each step.
9
10 You can view the results by interpreting the file:
11
12 $ dhall --file learndhall.dhall
13
14 {- Comments can be nested -}
15-}
16
17let greeting = "Hello, world!"
18
19let fruits = "🍋🍓🍍🍉🍌"
20
21let interpolation = "Enjoy some delicious fruit: ${fruits}"
22
23let multilineText {- Inline comments work, too -} =
24 ''
25 Leading whitespace is stripped from multi-line text literals.
26
27 That means you can freely indent or dedent a text literal without
28 changing the result.
29
30 Relative indentation within the literal is still preserved.
31
32 Other than that, the text literal is preserved verbatim, similar to a
33 "literal" YAML multiline string.
34 ''
35
36let bool = True
37
38-- Type annotations on bindings are optional, but helpful, so we'll use them
39let annotation : Bool = True
40
41let renderedBool : Text = if bool then "True" else "False"
42
43-- Natural numbers are non-negative and are unsigned
44let naturalNumber : Natural = 42
45
46-- Integers may be negative, but require an explicit sign, even if positive
47let positiveInteger : Integer = +1
48
49let negativeInteger : Integer = -12
50
51let pi : Double = 3.14159265359
52
53{- You can use a wider character range for identifiers (such as quotation
54 marks and whitespace) if you quote them using backticks
55-}
56let `Avogadro's Number` : Double = 6.0221409e+23
57
58let origin : { x : Double, y : Double } = { x = 0.0, y = 0.0 }
59
60let somePrimes : List Natural = [ 2, 3, 5, 7, 11 ]
61
62{- A schema is the same thing as a type
63
64 Types begin with an uppercase letter by convention, but this convention is
65 not enforced
66-}
67let Profile : Type
68 = { person :
69 { name : Text
70 , age : Natural
71 }
72 , address :
73 { country : Text
74 , state : Text
75 , city : Text
76 }
77 }
78
79let john : Profile =
80 { person =
81 { name = "John Doe"
82 , age = 67
83 }
84 , address =
85 { country = "United States"
86 , state = "Pennsylvania"
87 , city = "Philadelphia"
88 }
89 }
90
91let philadelphia : Text = john.address.city
92
93{- Enum alternatives also begin with an uppercase letter by convention. This
94 convention is not enforced
95-}
96let DNA : Type = < Adenine | Cytosine | Guanine | Thymine >
97
98let dnaSequence : List DNA = [ DNA.Thymine, DNA.Guanine, DNA.Guanine ]
99
100let compactDNASequence : List DNA =
101 let a = DNA.Adenine
102 let c = DNA.Cytosine
103 let g = DNA.Guanine
104 let t = DNA.Thymine
105 in [ c, t, t, a, t, c, g, g, c ]
106
107-- You can transform enums by providing a record with one field per alternative
108let theLetterG : Text =
109 merge
110 { Adenine = "A"
111 , Cytosine = "C"
112 , Guanine = "G"
113 , Thymine = "T"
114 }
115 DNA.Guanine
116
117let presentOptionalValue : Optional Natural = Some 1
118
119let absentOptionalValue : Optional Natural = None Natural
120
121let points : List { x : Double, y : Double } =
122 [ { x = 1.1, y = -4.2 }
123 , { x = 4.4, y = -3.0 }
124 , { x = 8.2, y = -5.5 }
125 ]
126
127{- `Natural -> List Natural` is the type of a function whose input type is a
128 `Natural` and whose output type is a `List Natural`
129
130 All functions in Dhall are anonymous functions (a.k.a. "lambdas"),
131 which you can optionally give a name
132
133 For example, the following function is equivalent to this Python code:
134
135 lambda n : [ n, n + 1 ]
136
137 ... and this JavaScript code:
138
139 function (n) { return [ n, n + 1 ]; }
140-}
141let exampleFunction : Natural -> List Natural =
142 \(n : Natural) -> [ n, n + 1 ]
143
144-- Dhall also supports Unicode syntax, but this tutorial will stick to ASCII
145let unicodeFunction : Natural → List Natural =
146 λ(n : Natural) → [ n, n + 1 ]
147
148-- You don't need to parenthesize function arguments
149let exampleFunctionApplication : List Natural =
150 exampleFunction 2
151
152let functionOfMultipleArguments : Natural -> Natural -> List Natural =
153 \(x : Natural) -> \(y : Natural) -> [ x, y ]
154
155let functionAppliedToMultipleArguments : List Natural =
156 functionOfMultipleArguments 2 3
157
158{- Same as `exampleFunction` except we gave the function's input type a
159 name: "n"
160-}
161let namedArgumentType : forall (n : Natural) -> List Natural =
162 \(n : Natural) -> [ n, n + 1 ]
163
164{- If you name a function's input type, you can use that name later within the
165 same type
166
167 This lets you write a function that works for more than one type of input
168 (a.k.a. a "polymorphic" function)
169-}
170let duplicate : forall (a : Type) -> a -> List a =
171 \(a : Type) -> \(x : a) -> [ x, x ]
172
173let duplicatedNumber : List Natural =
174 duplicate Natural 2
175
176let duplicatedBool : List Bool =
177 duplicate Bool False
178
179{- The language also has some built-in polymorphic functions, such as:
180
181 List/head : forall (a : Type) -> List a -> Optional a
182-}
183let firstPrime : Optional Natural = List/head Natural somePrimes
184
185let functionOfARecord : { x : Natural, y : Natural } -> List Natural =
186 \(args : { x : Natural, y : Natural }) -> [ args.x, args.y ]
187
188let functionAppliedToARecord : List Natural =
189 functionOfARecord { x = 2, y = 5 }
190
191{- All type conversions are explicit
192
193 `Natural/show` is a built-in function of the following type:
194
195 Natural/show : Natural -> Text
196
197 ... that converts `Natural` numbers to their `Text` representation
198-}
199let typeConversion : Natural -> Text =
200 \(age : Natural) -> "I am ${Natural/show age} years old!"
201
202-- A "template" is the same thing as a function whose output type is `Text`
203let mitLicense : { year : Natural, copyrightHolder : Text } -> Text =
204 \(args : { year : Natural, copyrightHolder : Text }) ->
205''
206Copyright ${Natural/show args.year} ${args.copyrightHolder}
207
208Permission is hereby granted, free of charge, to any person obtaining a copy of
209this software and associated documentation files (the "Software"), to deal in
210the Software without restriction, including without limitation the rights to
211use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
212of the Software, and to permit persons to whom the Software is furnished to do
213so, subject to the following conditions:
214
215The above copyright notice and this permission notice shall be included in all
216copies or substantial portions of the Software.
217
218THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
219IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
220FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
221AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
222LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
223OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
224SOFTWARE.
225''
226
227-- Template instantiation is the same thing as function application
228let templatedLicense : Text =
229 mitLicense { year = 2019, copyrightHolder = "Jane Smith" }
230
231{- You can import expressions by URL
232
233 Also, like Bash, you can import code from your local filesystem (not shown)
234
235 Security-conscious users can pin remotely-imported expressions by adding a
236 semantic integrity check. The interpreter rejects any attempt to tamper with
237 an expression pinned in this way. However, behavior-preserving refactors
238 of imported content will not perturb the hash.
239
240 Imported expressions pinned in this way are also locally cached in a
241 content-addressable store (typically underneath `~/.cache/dhall`)
242-}
243let Natural/sum : List Natural -> Natural =
244 https://prelude.dhall-lang.org/Natural/sum
245 sha256:33f7f4c3aff62e5ecf4848f964363133452d420dcde045784518fb59fa970037
246
247let twentyEight : Natural = Natural/sum somePrimes
248
249-- A "package" is the same thing as a (possibly nested) record that you can import
250let Prelude = https://prelude.dhall-lang.org/package.dhall
251
252let false : Bool = Prelude.Bool.not True
253
254-- You can import the raw contents of a file by adding `as Text` to an import
255let sourceCode : Text = https://prelude.dhall-lang.org/Bool/not as Text
256
257-- You can import environment variables, too:
258let presentWorkingDirectory = env:PWD as Text
259
260-- You can provide a fallback expression if an import fails
261let home : Optional Text = Some env:HOME ? None Text
262
263-- Fallback expressions can contain alternative imports of their own
264let possiblyCustomPrelude =
265 env:DHALL_PRELUDE
266 ? https://prelude.dhall-lang.org/package.dhall
267
268{- Tie everything together by auto-generating configurations for 10 build users
269 using the `generate` function:
270
271 Prelude.List.generate
272 : Natural -> forall (a : Type) -> (Natural -> a) -> List a
273-}
274let buildUsers =
275 let makeUser = \(user : Text) ->
276 let home = "/home/${user}"
277 let privateKey = "${home}/.ssh/id_ed25519"
278 let publicKey = "${privateKey}.pub"
279 in { home = home
280 , privateKey = privateKey
281 , publicKey = publicKey
282 }
283
284 let buildUser =
285 \(index : Natural) -> makeUser "build${Natural/show index}"
286
287 let Config =
288 { home : Text
289 , privateKey : Text
290 , publicKey : Text
291 }
292
293 in Prelude.List.generate 10 Config buildUser
294
295-- Present all of the results in a final record
296in { greeting = greeting
297 , fruits = fruits
298 , interpolation = interpolation
299 , multilineText = multilineText
300 , bool = bool
301 , annotation = annotation
302 , renderedBool = renderedBool
303 , naturalNumber = naturalNumber
304 , positiveInteger = positiveInteger
305 , negativeInteger = negativeInteger
306 , pi = pi
307 , `Avogadro's Number` = `Avogadro's Number`
308 , origin = origin
309 , somePrimes = somePrimes
310 , john = john
311 , philadelphia = philadelphia
312 , dnaSequence = dnaSequence
313 , compactDNASequence = compactDNASequence
314 , theLetterG = theLetterG
315 , presentOptionalValue = presentOptionalValue
316 , absentOptionalValue = absentOptionalValue
317 , points = points
318 , exampleFunction = exampleFunction
319 , unicodeFunction = unicodeFunction
320 , exampleFunctionApplication = exampleFunctionApplication
321 , functionOfMultipleArguments = functionOfMultipleArguments
322 , functionAppliedToMultipleArguments = functionAppliedToMultipleArguments
323 , namedArgumentType = namedArgumentType
324 , duplicate = duplicate
325 , duplicatedNumber = duplicatedNumber
326 , duplicatedBool = duplicatedBool
327 , firstPrime = firstPrime
328 , functionOfARecord = functionOfARecord
329 , functionAppliedToARecord = functionAppliedToARecord
330 , typeConversion = typeConversion
331 , mitLicense = mitLicense
332 , templatedLicense = templatedLicense
333 , twentyEight = twentyEight
334 , false = false
335 , sourceCode = sourceCode
336 , presentWorkingDirectory = presentWorkingDirectory
337 , home = home
338 , buildUsers = buildUsers
339 }
To learn more, visit the official website, which also lets you try the language live in your browser: