Haskell to Dhall Union Problem


#1

Hey all,

I have a Haskell program I’m using to generate some Dhall types. Here’s an example:

data B' = C Text | D Text
data A = A Text | B B'

I then have functions like aDecoder, aType and aText to pretty print the type as a Dhall type. The problem is that it gets printed like:

let A = < A : Text | B : < C Text | D Text > >

Which leaves consumers of this type unable to access the C and D constructors.

Next I tried adding a decoder for the B' type and printing it in the same file. Problem is, this is a completely different type from what the B constructor accepts. I tried hacking it by manually replacing the B constructor’s type with the B' type, and that solved things until it came to bringing the data back into Haskell, at which point I get a type error saying that C and D are not part of A.

Any idea how I can make this work?


#2

How is the B' type different from the B constructor?
Isn’t there an issue with the A printed type, shouldn’t it be < A : Text | B : < C : Text | D : Text > > ?


#3

My thoughts exactly. It’s syntactically the same, but semantically different enough that I get an error trying to decode it.

Interestingly, I just took my trivial example and tried it in GHCI and it seems like it worked. Here’s the code in case anyone else wants to try it:

import Dhall

:set -XDeriveGeneric
:set -XTypeApplications
:set -XDeriveAnyClass
:set -XOverloadedStrings

data B' = C Text | D Text deriving (Generic, FromDhall, Show)
data A = A Text | B B' deriving (Generic, FromDhall, Show)

let atyp = (Dhall.Core.pretty (Dhall.expected (Dhall.auto @A))) :: Text
let btyp = (Dhall.Core.pretty (Dhall.expected (Dhall.auto @B'))) :: Text

let result = ((Dhall.input (Dhall.auto) $ Data.Text.concat ["let Bp = ", btyp, "\nlet A = ", Data.Text.replace btyp "Bp" atyp, "\n", "let a : A = A.B (Bp.C \"Blah\") in a"]) :: IO A)

:type result
fmap show result

So I guess my question becomes: is there a way to make Dhall automatically leave the B' type extracted rather than inlining it? Maybe with something with the normalization settings?

Thanks!


#4

@joefiorini: I recommend doing things the other way around: generating Haskell types from Dhall types. Specifically, I recommend using the Dhall.TH.makeHaskellTypes utility for this purpose:

https://hackage.haskell.org/package/dhall-1.35.0/docs/Dhall-TH.html#v:makeHaskellTypes

The documentation for that utility explains how to use it. Adapting it slightly to your use case, you would basically define the Dhall types you want ahead of time, like this:

-- ./B.dhall
< C : Text | D : Text >
-- ./A.dhall
< A : Text | B : ./B.dhall >

… and then you would generate Haskell types from those like this:

Dhall.TH.makeHaskellTypes
    [ MultipleConstructors "B" "./B.dhall"
    , MultipleConstructors "A" "./A.dhall"
    ]

… which I believe would generate the Haskell types you expect:

data B = C Text | D Text

data A = A Text | B B

Another reason I recommend generating Haskell types from Dhall types is that in general it’s usually better if Dhall is the source of truth for the type definitions rather than Haskell, especially if you are interoperating with multiple language implementations that produce and consume the same Dhall types.


#5

Thank you, that does seem a decent idea, especially since I want to make this code is going to be an important part of the API for my project.

One more question, instead of having a separate Dhall file for each type, can I have them all exported from the same file and still import the types into Haskell?


#6

@joefiorini: Yes, they can all be exported from a single fie. For example, supposing that the file is named ./types.dhall then the Haskell code would change to this:

Dhall.TH.makeHaskellTypes
    [ MultipleConstructors "B" "(./types.dhall).B"
    , MultipleConstructors "A" "(./types.dhall).A"
    ]