dhall-lang / dhall-haskell

Maintainable configuration files
https://dhall-lang.org/
BSD 3-Clause "New" or "Revised" License
915 stars 213 forks source link

Interpolation fails on external templates/text #676

Closed Reisen closed 5 years ago

Reisen commented 5 years ago

Interpolation seems to happen at the file level. This may be intentional, but seems like a large shortcoming. As an example

-- Foo
\(env : { foo : Text, bar : Text }) -> ./Template
-- Template
''
Foo: ${env.foo}
Bar: ${env.bar}
''

If I try and run this with some data:

$ dhall <<< './Foo { foo = "Hello", bar = "World" }'

I receive an error about env being unbound. If I bring the template into the same file instead of trying to expand it with ./Template, everything works. I assume this has to do with scope being checked only within a files current expression, and not after being expanded into some other context. Perhaps there's a workaround for this with interpolation however?

sellout commented 5 years ago

This isn’t about interpolation, but rather dynamic vs lexical scoping. It’s no different than if you had

-- Foo
let env = { foo = "Hello", bar = "World" }
in ./Template
-- Template
env.foo ++ env.bar

I fall strongly on the side of disallowing any form of dynamic scoping, and there are plenty of places to read about the consequences of either online, so I won’t rehash the arguments here.

Reisen commented 5 years ago

That does makes sense, but I was hoping that the evaluation of interpolated text happened in a different pass I suppose.

So really what it is I'm looking for that we're missing is some form of built-in that allows for doing Text/interpolate context ./Template, or something along those lines. As it is right now I can do something like:

./Template as Text

To gain a raw string with the template in it, but going from that to something rendered is impossible.

Perhaps some built-in such as above, or a Template type similar to Text could help here? Using Dhall for templating is pretty powerful but it currently makes it difficult to work with "raw" template files programatically. For example, if I keep a bunch of template files in templates/ and I want to expose these to users to edit as text, I need to at run-time load these and then programatically wrap the text with a function prelude and '' to make them evaluable by Dhall.

Gabriella439 commented 5 years ago

@Reisen: Is there any reason why you cannot do this in your template file?

\(env : { foo : Text, bar : Text }) ->

''
Foo: ${env.foo}
Bar: ${env.bar}
''
sellout commented 5 years ago

@Gabriel439 It seems @Reisen’s goal is to have users write “plain text” template files where the rules are basically like “wrap vars in ${...}, and the vars available are foo, bar, ...”

The example shows a bit of a middle ground, which confuses things a bit … i.e., if you’re going to use something to do

"''\n" ++ templateFile ++ "''\n"

then you might as well do

"λ(env : { ... }) → ''\n" ++ templateFile ++ "''\n"

But I think @Reisen wants to have template files that look exactly like their output, modulo substitution in order to simplify things for the end-user.

Gabriella439 commented 5 years ago

@sellout: Yeah, I understand the goal. What I meant to ask is if it would be okay to retain the boilerplate at the top of the file and tell the less technical user to ignore that boilerplate.

Where I'm going with this is that I believe you can automate away a lot of the boilerplate by using an import to specify the type of the environment bound by each template, like this:

\(env : ./Environment) ->

''
…
''

That then provides two benefits:

Reisen commented 5 years ago

@Gabriel439 The main problem here is that the files themselves aren't created by me, they're spit out of another tool as markdown files written by other users who understand Markdown but aren't necessarily technical. Of course I can try and push the issue down the line to the users and have them write headers in the Markdown they write, but I want to avoid that. So right now my only real choice is to do a kind of:

fold [ headerFor "SomeInputType", "''", markdownContent, "''" ]

To produce an input I can use for Dhall.

I want to as much as possible avoid inflicting knowledge of Dhall onto the end-user in the templating stage, while pulling the benefits of getting type-checked templating in my consuming code. Dhall is the closest to being able to do this, giving me errors at evaluation time that I just don't get with E-DE, and I lose flexibility with Shakespear.

Of course, I can survive with the run-time folding of a mini-boilerplate prelude in my code, but it just seemed like something that should be possible with a Text/interpolate of some kind.

Importing the Environment as you mentioned happens to be something I do right now using Typeable to type the right context at run-time. Basically the end goal is this:

Gabriella439 commented 5 years ago

@Reisen: Is there a tooling limitation why the users can't insert the boilerplate? The reason I ask is that if the Markdown-are tool they use to edit the template fails on the header, then it's likely to also fail on anything more sophisticated than interpolating variables that already have type Text. However, it sounds like you plan to interpolate things more complex than that if you are interested in Dhall's type-checking feature.

Reisen commented 5 years ago

@Gabriel439 I've given this a bit more thought and I think my initial opening of this ticket was mostly just a kneejerk reaction coming from other templating languages. After having pushed this as our project choice, I've found:

I'm going to go ahead and close this ticket. I still think there may be some value in being able to use something along the lines of Text/interpolate within Dhall, but it is no longer an issue for our project. Thanks for giving this issue some thought as well! Dhall is absolutely fantastic.

Gabriella439 commented 5 years ago

@Reisen: You're welcome! :slightly_smiling_face: