Giter Site home page Giter Site logo

Recursive data types about dhall-lang HOT 26 CLOSED

dhall-lang avatar dhall-lang commented on September 26, 2024
Recursive data types

from dhall-lang.

Comments (26)

Gabriella439 avatar Gabriella439 commented on September 26, 2024 2

@mwgkgk: You might also find these resources helpful, too:

from dhall-lang.

jberryman avatar jberryman commented on September 26, 2024 2

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.

ocharles avatar ocharles commented on September 26, 2024 1

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024 1

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:

dhall-lang/dhall-haskell#223

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024 1

@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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024 1

@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.

Profpatsch avatar Profpatsch commented on September 26, 2024

My use case is specifying a small eDSL for (reactjs) slides in dhall and translating them to HTML afterwards.

from dhall-lang.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

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.

Profpatsch avatar Profpatsch commented on September 26, 2024

Oh! That looks like a GADT.

from dhall-lang.

Profpatsch avatar Profpatsch commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

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.

Profpatsch avatar Profpatsch commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

Yes, the explicit Recurse method would also require a manual Interpret instance, too

from dhall-lang.

Profpatsch avatar Profpatsch commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

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.

ocharles avatar ocharles commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

@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.

ocharles avatar ocharles commented on September 26, 2024

Ah yes, that makes sense. πŸ‘ to a FAQ entry, if we don't get some first class syntax for this.

from dhall-lang.

mwgkgk avatar mwgkgk commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

@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.

mwgkgk avatar mwgkgk commented on September 26, 2024

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.

denizdogan avatar denizdogan commented on September 26, 2024

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.

Gabriella439 avatar Gabriella439 commented on September 26, 2024

@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.

kevroletin avatar kevroletin commented on September 26, 2024

@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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.