Dhall

Личный сайт Go-разработчика из Казани

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: