Open lpil opened 4 years ago
By curiosity, did you consider something like jsx?
pub fn my_template(name: String, scores: List(Int)) -> HTMLTree =
<>
<h1>{name}</h1>
<score_list scores={scores} />
</>
pub fn score_list(scores: List(int)) -> HTMLTree =
case scores {
[] -> <p>No scores</p>
list -> <ul><score_list_items(list) /></ul>
}
Well, by writing the example, I can see that it will cumbersome to manipulate lists and maps like this. And I suppose that it will make the gleam grammar more complicated, with the different roles of <
and >
.
By curiosity, did you consider something like jsx?
See also this experiment in Elm: https://github.com/pzavolinsky/elmx
JSX is only useful for HTML and is more complicated to implement compared to templates. It also performs much worse than templates unless we do a lot of optimisations.
If we're to go down the JSX route we'd need some strong benefits to counteract those drawbacks.
To come back to templates, I think that several frameworks may want to do things a bit differently and it is a good idea to let them. For example, for layouts, would it be possible to have something like this:
// Application code
pub fn render_scores(name: String, scores: List(Int)) -> String =
let renderer = framework.template("scores")
renderer(name, scores)
// Framework
pub fn renderer(template: String) -> Dynamic
fn () {
let content = derive Template(file: strings.append("../views/", template))
derive Template(file: "../views/layout")
}
I don't see how to make it works with the static typing, but if we can, I think there is no need to have layouts in the language, it could be something added in userland. For partials, I think it would be the same: the framework can inject a partial
function.
But, it looks like that, without macros, layout and partials should be in the templating language. If we take a syntax inspired from handlebars, it could give:
{{#import my/helper }}
{{#> my_layout }}
{{> my_title_partial name=name}}
<ul>
{{#each scores}}
<li>{{ helper.human_readable_number(_) }}
{{/each}}
</ul>
{{/layout}}
Default escaping is HTML. A different escape method can be provided when the function is defined.
I think it is a good idea to have escaping by default, but bypassing it can be useful on some cases (like avoiding to escape twice the content
from my framework example). I know two ways to do that:
{{{wont_be_escaped}}}
)The above code for the application and framework code won't be possible as we cannot dynamically derive values, and the paths have to be static at compile time for them to be type checked and generated by the compiler. That's why deriving has a top level syntax rather than being inside a function.
I think it is a good idea to have escaping by default, but bypassing it can be useful on some cases (like avoiding to escape twice the content from my framework example). I know two ways to do that:
Yes, good points!
One thing to consider is that we want the type system to help us avoid unsafe use of escaped values. For HTML I think this means introducing a html type, and having html templates return this type.
If the value being interpolated into the template is a Html
then it is not escaped, if it is a String
or Iodata
then it is escaped. I've included more information in the original post.
I'm wondering why this issue is relevant here. It sounds to me that at templating language should be a library implemented in userland, not in the Gleam compiler.
Sounds like you are proposing this to be a core language feature. There are many ways to do templating language, with different trade-offs, letting libraries experiment and do this could be better to settle on the best approach.
Does the language need to concern itself with this? Maybe what is needed is some way to allow libraries to run and generate Gleam code at compile time.
It's not possible to implement type safe templates in userland without a macro system, which Gleam does not have. Because of this I'm considering this suitable for inclusion in the compiler.
It's not uncommon for a templating language to be maintained by the core team of a language, examples include Elixir, Go, and Ruby.
Maybe what is needed is some way to allow libraries to run and generate Gleam code at compile time.
A macro system like this would be orders of magnitude more work, I don't expect we will be able to do this within the next couple years if at all.
One thing to consider is that we want the type system to help us avoid unsafe use of escaped values. For HTML I think this means introducing a html type, and having html templates return this type.
I don't know if this is the right place to talk about this, especially since it's a bit larger in scope than what you're suggesting, but you can also use the type system to model privileged information as it relates to templating. If template variables and templates are tagged with their privilege level, then you can restrict sensitive information from ever appearing in a "less sensitive" template.
Using janky and poorly modeled ML pseudocode as an example:
data PublicInformation = PublicInformation String
data PrivateInformation = PrivateInformation String
data TemplateVariable a = TemplateVariable String a
data UnprivilegedView = UnprivilegedView { template :: String
, variables :: [TemplateVariable PublicInformation]}
data PrivilegedView = PrivilegedView { template :: String
, publicVariables :: [TemplateVariable PublicInformation]
, privateVariables :: [TemplateVariable PrivateInformation] }
data View = Privileged PrivilegedView
| Unprivileged UnprivilegedView
Under such a type system, values need to be tagged with their security level before they can appear in a template. Certain classes of data then, such as credit card numbers, can be constructed such that the compiler prevents them from ever appearing in a public template. Obviously the code above isn't very ergonomic to work with, but I think the general idea could be made to work more nicely. In general, if you model data sensitivity with a lattice, you can prevent the flow of sensitive data into less sensitive contexts.
All that being said, it's possible that a system like this could be implemented as a library that makes use of the core templating language.
This is another interesting area to look at, though I'm not sure there is a one size fits all solution, so userland implementation is more attractive
If you've not seen it before check out Granule, they have some really interesting stuff on this area.
Do we support layouts?
Do we support partials/includes?
Is there any reason why they would need to be included. I wrote two libraries, EExHTML and Raxx.View separately in part to keen concepts like this out of core EExHTML, however maybe Elixir makes it easier to add them as a layer afterwards, but I would only add them if you have to, there are a few choices that need to be made for both of these features that it would be nice not to think about on the first version
{% import my/helper %} <h1>{{ name }}</h1> <ul> {% for score in scores %} <li>{{ helper.human_readable_number(score) }} {% end %} </ul>
What about a Elm or F# inspired syntax?
h1 [ ]
[ str name ]
ul [ ]
(List.map (score -> li [ ] [ helper.human_readable_number(score) ]) scores)
Apologies for the terrible psuedocode
This is not a HTML generation system but a generic string templating system that could be used to render strings containing any content in any format. i.e. HTML, XML, plain text, yaml, toml, JavaScript, etc.
A function based HTML generation DSL could be implemented today in pure Gleam, no additional compiler features would be required :)
One possibility is to use string interpolation, similar to Scala:
import my/helper
pub fn my_template(name: String, scores: List(Int)) -> String =
html"""
<h1>$name</h1>
<ul>
${helper.each(scores, fn(score) {
html"<li>${helper.human_readable_number(score)}</li>"
})}
</ul>
"""
In Scala, strings that are prefixed with an identifier are interpolated. In interpolated strings, $something
and ${...}
are parsed as expressions, and they are desugared (i.e. replaced at compile time) to a function call, for example:
html"<ul><li>$name</li><li>$id</li></ul>"
// is desugared to
new StringContext("<ul><li>", "</li><li>", "</li></ul>").html(name, id)
// or, in Gleam it could be desugared to
html(["<ul><li>", "</li><li>", "</li></ul>"], [name, id])
The nice thing about this is that the string interpolation is dynamic (template strings can contain any number of parameters, and can do anything with them (e.g. HTML escaping)), but the compiler can still verify that all required parameters are present. String interpolation is used pervasively in Scala, since it is very powerful and flexible.
With the current work on a type provider design I think this could in future be implemented in userland.
Default escaping is HTML. A different escape method can be provided when the function is defined.
Do we support layouts?
Do we support partials/includes?
Should the file path be relative to the file? Or in
templates
at the base of the project? Or something else?