Comments (26)
@mwgkgk: You might also find these resources helpful, too:
- Automatic synthesis of typed Ξ-programs on term algebras - The original paper that this is based on
- Data is Code - A post I wrote describing this trick in the context of
morte
/annah
(the predecessors ofdhall
)
from dhall-lang.
Is there any way (in theory) to hide the complexities of translating recursive types in some syntactic sugar or language feature? I'm just getting started with dhall and have a really poor intuition for this.
from dhall-lang.
I just ran into this and would love for this to be at least documented somewhere, perhaps a FAQ?
My use case was wanting to encode what a Nix derivation is, in Dhall. I started with (in Derivation.dhall
)
{
require : List ./Derivation.dhall
}
and got immediately stuck.
In my case, knowing how to translate
data A = A { as :: [a], b :: B }
Would be very helpful.
from dhall-lang.
I added a guide on how to do this to the Dhall wiki:
https://github.com/dhall-lang/dhall-lang/wiki/How-to-translate-recursive-code-to-Dhall
... and I also mention this in the Haskell tutorial's FAQ section in this pull request:
This is part of an effort to get more documentation migrated to the language-independent Dhall wiki
Once that pull request is merged then I'll mark this closed
from dhall-lang.
@jberryman: There might be some things we could do to make the type-level representation easier to read, but the term-level implementation stuff is going to be difficult to simplify
from dhall-lang.
@kevroletin: There is a special tree-like recursive structure which dhall-to-yaml
supports which is the Prelude.JSON.Type
type:
https://store.dhall-lang.org/Prelude-v20.2.0/JSON/Type.dhall.html
β¦ so this is one way that you could encode a Dhall data structure that produces that result:
-- ./example.dhall
let JSON = https://prelude.dhall-lang.org/JSON/package.dhall
let MakePerson
: { children : List JSON.Type, name : Text } β JSON.Type
= Ξ»(x : { children : List JSON.Type, name : Text }) β
JSON.object
( toMap
{ children = JSON.array x.children, name = JSON.string x.name }
)
let example
: JSON.Type
= MakePerson
{ name = "John"
, children =
[ MakePerson { name = "Mary", children = [] : List JSON.Type }
, MakePerson { name = "Jane", children = [] : List JSON.Type }
]
}
in example
$ dhall-to-yaml --file example.dhall
children:
- children: []
name: Mary
- children: []
name: Jane
name: John
from dhall-lang.
My use case is specifying a small eDSL for (reactjs) slides in dhall and translating them to HTML afterwards.
from dhall-lang.
It is definitely possible! Although the language doesn't provide built-in support for recursive types, you can still encode them within the language using a mechanical translation process, like this:
$ cat Node
let Node : Type =
β(Node : Type)
β β( Recurse
: < Plain : Text
| Heading : { level : Natural, heading : Text }
| Listing : List Node
>
β Node
)
β Node
in Node
$ cat Plain
let Plain : Text β ./Node =
Ξ»(x : Text)
β Ξ»(Node : Type)
β Ξ»( Recurse
: < Plain : Text
| Heading : { level : Natural, heading : Text }
| Listing : List Node
>
β Node
)
β Recurse
< Plain = x
| Heading : { level : Natural, heading : Text }
| Listing : List Node
>
in Plain
$ cat Heading
let Heading : { level : Natural, heading : Text } β ./Node =
Ξ»(x : { level : Natural, heading : Text })
β Ξ»(Node : Type)
β Ξ»( Recurse
: < Plain : Text
| Heading : { level : Natural, heading : Text }
| Listing : List Node
>
β Node
)
β Recurse
< Heading = x
| Plain : Text
| Listing : List Node
>
in Heading
$ cat Listing
let map = https://ipfs.io/ipfs/QmQ8w5PLcsNz56dMvRtq54vbuPe9cNnCCUXAQp6xLc6Ccx/Prelude/List/map
in let Listing : List ./Node β ./Node =
Ξ»(x : List ./Node)
β Ξ»(Node : Type)
β Ξ»( Recurse
: < Plain : Text
| Heading : { level : Natural, heading : Text }
| Listing : List Node
>
β Node
)
β let f : ./Node β Node =
Ξ»(x : ./Node) β x Node Recurse
in Recurse
< Listing = map ./Node Node f x
| Plain : Text
| Heading : { level : Natural, heading : Text }
>
in Listing
... and here is an example of how a user would use this:
$ cat example
./Listing
[ ./Plain "ABC"
, ./Listing
[ ./Heading { level = +0, heading = "foo" }
, ./Listing
[ ./Heading { level = +1, heading = "bar" }
]
, ./Plain "DEF"
]
, ./Plain "GHI"
]
... which normalizes to:
$ dhall <<< './example'
β(Node : Type) β β(Recurse : < Heading : { heading : Text, level : Natural } | Listing : List Node | Plain : Text > β Node) β Node
Ξ»(Node : Type) β Ξ»(Recurse : < Heading : { heading : Text, level : Natural } | Listing : List Node | Plain : Text > β Node) β Recurse < Listing = [Recurse < Plain = "ABC" | Heading : { heading : Text, level : Natural } | Listing : List Node >, Recurse < Listing = [Recurse < Heading = { heading = "foo", level = +0 } | Listing : List Node | Plain : Text >, Recurse < Listing = [Recurse < Heading = { heading = "bar", level = +1 } | Listing : List Node | Plain : Text >] : List Node | Heading : { heading : Text, level : Natural } | Plain : Text >, Recurse < Plain = "DEF" | Heading : { heading : Text, level : Natural } | Listing : List Node >] : List Node | Heading : { heading : Text, level : Natural } | Plain : Text >, Recurse < Plain = "GHI" | Heading : { heading : Text, level : Natural } | Listing : List Node >] : List Node | Heading : { heading : Text, level : Natural } | Plain : Text >
... or formatted for readability:
Ξ»(Node : Type)
β Ξ»(Recurse : < Heading : { heading : Text, level : Natural } | Listing : List Node | Plain : Text > β Node)
β Recurse < Listing =
[ Recurse < Plain = "ABC" | Heading : { heading : Text, level : Natural } | Listing : List Node >
, Recurse < Listing =
[ Recurse < Heading = { heading = "foo", level = +0 } | Listing : List Node | Plain : Text >
, Recurse < Listing =
[ Recurse < Heading = { heading = "bar", level = +1 } | Listing : List Node | Plain : Text >
] : List Node | Heading : { heading : Text, level : Natural } | Plain : Text >
, Recurse < Plain = "DEF" | Heading : { heading : Text, level : Natural } | Listing : List Node >
] : List Node | Heading : { heading : Text, level : Natural } | Plain : Text >
, Recurse < Plain = "GHI" | Heading : { heading : Text, level : Natural } | Listing : List Node >
] : List Node | Heading : { heading : Text, level : Natural } | Plain : Text >
from dhall-lang.
Actually, thinking about this more, it might be easier to just hand-write the latter form. In other words, just introduce the two bound variables:
Ξ»(Node : Type)
β Ξ»(Recurse : < Heading : { heading : Text, level : Natural } | Listing : List Node | Plain : Text > β Node)
β ...
... and then wrap each level of nesting in Recurse
when defining the nested data type
from dhall-lang.
I forgot to mention that if you don't care about using anonymous unions specifically, there is a less verbose encoding:
Ξ»(Node : Type)
β Ξ»(Plain : Text β Node)
β Ξ»(Heading : { level : Natural, heading : Text } β Node)
β Ξ»(Listing : List Node β Node)
β Listing
[ Plain "ABC"
, Listing
[ Heading { level = +0, heading = "foo" }
, Listing
[ Heading { level = +1, heading = "bar" }
]
, Plain "DEF"
]
, Plain "GHI"
]
That's probably easier to read and directly manipulate
from dhall-lang.
Oh! That looks like a GADT.
from dhall-lang.
Wait; how can I create something β Node
then? e.g. what would a valid definition of Plain
be?
Update: And how would I marshal it into a Haskell data
afterwards?
from dhall-lang.
So the type of ./Node
would be:
-- ./Node
β(Node : Type)
β β(Plain : Text β Node)
β β(Heading : { level : Natural, heading : Text } β Node)
β β(Listing : List Node β Node)
β Node
... and then you would implement Plain
as:
-- ./Plain
let Plain : Text β ./Node =
Ξ»(x : Text)
β Ξ»(Node : Type)
β Ξ»(Plain : Text β Node)
β Ξ»(Heading : { level : Natural, heading : Text } β Node)
β Ξ»(Listing : List Node β Node)
β Plain x
in Plain
Marshaling it into Haskell data via Interpret
would be trickier, but still possible. You wouldn't be able to do it by just deriving Generic
and Interpret
. Instead, you would have to hand-write the Interpret
instance that loops over the syntax tree, like this:
data Node
= Plain Text
| Heading { level :: Int, heading :: Text }
| Listing [Node]
instance Interpret Node where
autoWith options = Type {..}
where
-- Actually, this is not completely correct as you need to handle the more general case where
-- `"Node"` is Ξ±-renamed to another type variable
expected =
Pi "Node" Type (Pi _ (Pi _ Text "Node") ...)
-- Same thing here: you need to handle the more general case where the lamba-bound
-- variables are renamed, or if any of the names shadow each other
extract (Lam "Node" _ (Lam "Plain" _ (Lam "Heading" (Lam "Listing" _ e0)))) = buildNode e0
extract _ = Nothing
where
buildNode (App (Var (V "Plain 0)) (TextLit t)) = Just (Plain t)
...
buildNode _ = Nothing
The basic idea is that once you skip all the lambdas in the syntax tree it's basically a very verbose encoding of your data type using Dhall's syntax tree and buildNode
just loops over that and marshals it into the Haskell Node
type, returning Nothing
if it runs into something unexpected
from dhall-lang.
Thatβs all very verbose. I definitely wouldnβt have found out how to unmarshal into Haskell.
I suspect the explicit Recurse
method above would involve a similar manual Interpret
instance as well?
from dhall-lang.
Yes, the explicit Recurse
method would also require a manual Interpret
instance, too
from dhall-lang.
Hm, do you think it would be possible to write these instances once for any kind of correctly formatted Dhall-expression? e.g. by interpreting into Fix
?
from dhall-lang.
I don't think it's possible mainly due to a limitation in how GHC.Generics
work. In fact, you'll notice that the dhall
library actually already lets you derive an Interpret
instance for a recursive type without any complaints, but if you try to use the instance the program will crash with a stack overflow
The problem is that GHC.Generics
has a very simplistic way of dealing with recursion so that if you define a type like:
data Stream = Stream { head :: Integer, tail :: Stream }
... then the derived Interpret
implementation will try to decode the following infinite type:
{ head : Integer, tail : { head : Integer, tail : { head : Integer, tail : ...
As far as I can tell there is no way to do something smarter with the GHC.Generics
machinery
from dhall-lang.
Ok, I think it's this:
Derivation.dhall
:
let Derivation : Type =
forall (Derivation : Type) ->
forall (MkDerivation : List Derivation -> Derivation) ->
Derivation
in Derivation
And derivation.dhall
:
let map = https://ipfs.io/ipfs/QmQ8w5PLcsNz56dMvRtq54vbuPe9cNnCCUXAQp6xLc6Ccx/Prelude/List/map
in
let derivation : List ./Derivation.dhall -> ./Derivation.dhall =
\(inputs : List ./Derivation.dhall) ->
\(Derivation : Type) ->
\(MkDerivation : List Derivation -> Derivation) ->
let f : ./Derivation.dhall -> Derivation =
\(x : ./Derivation.dhall) -> x Derivation MkDerivation in
MkDerivation (map ./Derivation.dhall Derivation f inputs)
in derivation
from dhall-lang.
@ocharles: Yes, that's correct. You basically assume that you have the constructor for the recursive type.
I would only suggest a slightly different approach that is a little simpler to encode. Using a modified version of the example you asked for:
data A = MkA { children :: [A], name :: Text }
The Dhall encoding of that type would be:
β(A : Type) β β(MkA : { children : List A, name : Text } β A) β A
And this would be an example value of that type:
Ξ»(A : Type)
β Ξ»(MkA : { children : List A, name : Text } β A)
β MkA
{ children =
[ MkA { children = [] : List A, name = "John" }
, MkA { children = [] : List A, name = "Mary" }
]
, name = "Ted"
}
I can add an example like that to the FAQ section
from dhall-lang.
Ah yes, that makes sense. π to a FAQ entry, if we don't get some first class syntax for this.
from dhall-lang.
Is it possible to import children in the above example?
Ξ»(A : Type)
β Ξ»(MkA : { children : List A, name : Text } β A)
β MkA
{ children =
[ ./value2.dhall
, MkA { children = [] : List A, name = "Mary" }
]
, name =
"Ted"
}
Where value2.dhall
is simply
MkA { children = [] : List A, name = "John" }
This throws Unbound variable MkA
.
I cannot say that I'm 100% confident in what's going on though, and how to achieve uniform look for values that form a dependency graph while spread over a catalogue of files.
from dhall-lang.
@mwgkgk: Dhall does not support importing "open" expressions (i.e. expressions with free variables), but there might be another solution to what you have in mind
You can create separate importable files that behave equivalently to the corresponding types and constructors. The general trick for this is known as Boehm-Berarducci encoding and was created for the purpose of being able to separate each type and term into its own "closed" expression (i.e. an expression with no free variables) while still supporting things like recursion.
Using the example you gave, the way you would split this into multiple files would be:
-- ./A
let A
: Type
= β(A : Type) β β(MkA : { children : List A, name : Text } β A) β A
in A
-- ./MkA
let map =
https://raw.githubusercontent.com/dhall-lang/Prelude/e44284bc37a5808861dacd4c8bd13d18411cb961/List/map
in let MkA
: { children : List ./A, name : Text } β ./A
= Ξ»(oldInput : { children : List ./A, name : Text })
β Ξ»(A : Type)
β Ξ»(MkA : { children : List A, name : Text } β A)
β let adapt : ./A β A = Ξ»(a : ./A) β a A MkA
in MkA
( { children = map ./A A adapt oldInput.children }
β§ oldInput.{ name }
)
in MkA
-- ./example
let example : ./A = ./MkA { children = [] : List ./A, name = "John" }
in example
-- ./example2
let example2
: ./A
= ./MkA
{ children =
[ ./MkA { children = [] : List ./A, name = "John" }
, ./MkA { children = [] : List ./A, name = "Mary" }
]
, name =
"Ted"
}
in example2
$ dhall <<< './example'
β(A : Type) β β(MkA : { children : List A, name : Text } β A) β A
Ξ»(A : Type)
β Ξ»(MkA : { children : List A, name : Text } β A)
β MkA { children = [] : List A, name = "John" }
$ dhall <<< './example2'
β(A : Type) β β(MkA : { children : List A, name : Text } β A) β A
Ξ»(A : Type)
β Ξ»(MkA : { children : List A, name : Text } β A)
β MkA
{ children =
[ MkA { children = [] : List A, name = "John" }
, MkA { children = [] : List A, name = "Mary" }
]
, name =
"Ted"
}
from dhall-lang.
This is sort of amazing. I find that I struggle a lot with the signatures here, but it is likely expected.
(Commenting to express appreciation rather than request further help.)
from dhall-lang.
Are improvements to this aspect of Dhall on a roadmap somewhere? I don't mean to complain at all, and this is a bit over my head when it comes to type theory, but this is the first time I think "well...that's a bummer."
The smart constructor feature goes a long way for the end-users, but if you're working with complex record structures, the smart constructors themselves can become unreadable to the point where I almost go "you can't do that in Dhall".
So having imagined that this should be possible:
let Person = { name : Text, children: List Person }
...this doesn't taste quite as good:
let Person
: Type
= β(Person : Type) β
β(MakePerson : { children : List Person, name : Text } β Person) β
Person
let MakePerson
: { children : List Person, name : Text } β Person
= Ξ»(x : { children : List Person, name : Text }) β
Ξ»(_Person : Type) β
Ξ»(MakePerson : { children : List _Person, name : Text } β _Person) β
let adapt
: Person β _Person
= Ξ»(y : Person) β y _Person MakePerson
in MakePerson
(x with children = List/map Person _Person adapt x.children)
from dhall-lang.
@denizdogan: Yeah, the main issue is that it's really difficult to add language support for recursion while keeping the language total. This is compounded by the fact that we need to support multiple language bindings and it's already hard enough as it is to implement a new language binding
from dhall-lang.
@Gabriel439
AFAIU in the examples above
- in dhall we represented a recursive data structure as code
- in dhall we interpreted "data as code" into a flat list
- in Haskell we interpreted "data as code" into a recursive tree
I am trying to generate a config like the below from dhall. Can I do it without using Haskell just with dhall-to-yaml? The resulting yaml (or json) contains a representation of a tree:
- name: John
children:
- name: Mary
children: []
- name: Jane
children: []
The imaginary use cases for such configs are:
- build pipelines : one might represent parallel build steps as children of a parent node
- expressing inclusion relationships for events (for example a monitoring tool might use it to produce reports)
I am very new to dhall but I see that it supports a list (which is a recursive data structure). Maybe there might be a [magic] implementation of a tree data structure?
from dhall-lang.
Related Issues (20)
- `with` record update syntax removes other record fields HOT 4
- ABNF grammar should list "as Bytes" import mode
- Should the ABNF grammar disallow shebangs inside expressions? HOT 1
- eta-reducing to merge HOT 4
- Builtins operators reference: Missing record projection HOT 2
- ABNF grammar should explicitly disallow keywords as identifiers? HOT 2
- ABNF grammar should include a mandatory whitespace after `import-hashed`? HOT 1
- Improvements and fixes in the standard documentation HOT 3
- Is this an incorrect test file: `dhall-lang/tests/import/success/unit/ImportRelativeToHomeB.dhall`? HOT 2
- Link to non existing tweet
- A minimalistic proposal for do-notation
- Thoughts on introducing a minimum amount of type inference in Dhall HOT 1
- Introduce Bytes/length and Text/length as built-ins? HOT 3
- Is there a security hole: malicious sha256-protected cached content? HOT 1
- Eta-equivalence in `assert`? HOT 13
- A type level equivalent of the `with` keyword HOT 11
- Convert assertions to Leibniz equality types HOT 3
- A minimalistic proposal for adding row and column polymorphism to Dhall HOT 2
- A proposal for a "lightweight Dhall implementations" standard
- Year, Month, and Date extraction from a Date HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from dhall-lang.