A Unison implementation of the Dhall configuration language.
To install the latest version in your Unison codebase use the following ucm command:
pull hagl.public.dhall .lib.dhall
The main interface for this library are the functions
evaluate : Text ->{IO} Either Text DhallValue
evaluateSimple : Text -> Either Text DhallValue
Both evaluate a text containing a dhall expression into an error or a value of type DhallValue
.
unique type DhallValue
= DhallInteger Integer
| DhallNatural Natural
| DhallFloat Float
| DhallList [DhallValue]
| DhallBoolean Boolean
| DhallText Text
| DhallRecord (Map Text DhallValue)
| DhallOptional (Optional DhallValue)
evaluate
supports all of Dhall's features. In order to resolve imports (i.e. environment variables, local or remote files) it needs the IO
ability.
evaluateSimple
is a pure function and can be used to evaluate a self-contained dhall expression (i.e. without any imports). It will fail and return a Left
value when encountering an import during evaluation.
Let's have a look at this Dhall program from the Dhall homepage
let Config : Type =
{ home : Text
, privateKey : Text
, publicKey : Text
}
let makeUser : Text -> Config = \(user : Text) ->
let home : Text = "/home/${user}"
let privateKey : Text = "${home}/.ssh/id_ed25519"
let publicKey : Text = "${privateKey}.pub"
let config : Config = { home, privateKey, publicKey }
in config
let configs : List Config =
[ makeUser "bill"
, makeUser "jane"
]
in configs
Assume we want to use the above Dhall program to generate a list of Unison UserConfig values
unique type UserConfig = UserConfig Text Text Text
This mapping function converts the evaluated DhallValue into UserConfig by pattern matching on the structure. Since the Dhall type-checker verifies that the result has the correct structure, we can use non-exhaustive pattern matching:
convertConfigs : DhallValue -> List UserConfig
convertConfigs = cases
DhallList list ->
List.map (cases DhallRecord map ->
getString key = Map.get key map |> cases (Optional.Some (DhallText text)) -> text
UserConfig (getString "home") (getString "publicKey") (getString "privateKey")) list
-- evaluate and convert input in a watch expression
> evaluateSimple input |> Either.mapRight convertConfigs
The watch expression will give the following output in ucm
> evaluateSimple input |> Either.mapRight convertConfigs
⧩
Right
[ UserConfig
"/home/bill" "/home/bill/.ssh/id_ed25519.pub" "/home/bill/.ssh/id_ed25519",
UserConfig
"/home/jane" "/home/jane/.ssh/id_ed25519.pub" "/home/jane/.ssh/id_ed25519" ]
This example is also available online on Unison Share
This library also ships an unsupported runnable dhallRun
function that can be used to quickly test a dhall expression directly from ucm
.
The implementation uses a bit of a hack to not require quoting or escaping dhall code inside ucm. Here are a few examples what you can do:
Simple calculations
.> run dhallRun 47 * 71
3337
Text interpolation, access to environment variables and calling built-in functions
.> run dhallRun "Hello ${env:USER as Text}!\n 3 + 4 = ${Natural/show (3 + 4)}"
"Hello harald!
3 + 4 = 7"
Defining and applying functions
.> run dhallRun let add = \(x: Natural) -> \(y: Natural) -> x + y in add 1 2
3
Using remote dhall expressions (e.g. fold from Dhall prelude)
.> run dhallRun let sum = \(l: List Natural) -> https://prelude.dhall-lang.org/List/fold.dhall Natural l Natural (\(x: Natural) -> \(y: Natural) -> x + y) 0 in sum [1,2,3,4,5]
15
Loading and evaluating the remote expression takes some time. By adding a semantic hash to the import, the function can be cached locally. This will reduce the runtime of the second call below. You can use dhallHash
to calculate the hash value of an import expression:
.> run dhallRun https://prelude.dhall-lang.org/List/fold.dhall
sha256:10bb945c25ab3943bd9df5a32e633cbfae112b7d3af38591784687e436a8d814
.> run dhallRun let sum = \(l: List Natural) -> https://prelude.dhall-lang.org/List/fold.dhall sha256:10bb945c25ab3943bd9df5a32e633cbfae112b7d3af38591784687e436a8d814 Natural l Natural (\(x: Natural) -> \(y: Natural) -> x + y) 0 in sum [1,2,3,4,5]
15
.> run dhallRun let sum = \(l: List Natural) -> https://prelude.dhall-lang.org/List/fold.dhall sha256:10bb945c25ab3943bd9df5a32e633cbfae112b7d3af38591784687e436a8d814 Natural l Natural (\(x: Natural) -> \(y: Natural) -> x + y) 0 in sum [1,2,3,4,5]
15
See the Dhall documentation for more details about the language.
This project is currently in development, release v3 has alpha status.
The Dhall Acceptance Tests can be run with the the from ucm
with the command
run .external.dhall.trunk.testsuite.runTestSuite <path to local copy of github.com/dhall-lang/dhall-lang>
At the time of this writing this will give the following results
1495 total tests ( ✅ 1492 passed, 🚫 3 failed) in directory ./dhall-lang
Duration: 567.007s
The remaining failing tests are
dhall-lang/tests/type-inference/success/preludeA.dhall
: This test recursively loads all of prelude and shows some performance problems in the current implementation. It would probably succeed, but would take hours/days to do so. See the issues labeled performance for some ideas how to improve the performance../dhall-lang/tests/parser/failure/spacing/LetNoSpace3
./dhall-lang/tests/parser/failure/spacing/LetNoSpace4
The following features are not yet supported
- date & time types are not exposed in the resolved DhallValue, since there are no matching type in the Unsion standard library
Once Unison has some reflection/meta-programming possibilities it should be possible to
- generate a Dhall type for a Unison type (giving type checking & editor support for config files)
- automatically convert a Dhall value into a Unison value when their types are compatible
All code in this repository is available under the 3-Clause BSD License.
Copyright 2021-2022 Harald Gliebe
This project is based on and uses code from the projects