Hello, Ivan and anyone else who is interested--
Having just finished successfully converting a web site from Python (Flask
framework) to Chicken Scheme, I'd like to summarize my observations on the
Ersatz template library. And let me say, Ivan, that although I have a few
criticisms, I very much appreciate the work you have done to make Ersatz
available. Before I discovered it, I was thinking about developing a
Jinja-like template library myself, but I doubt that I could have done it
as well as you have. And overall I find that Ersatz works very well and is
not too difficult to use.
UNIMPLEMENTED JINJA2 FEATURES
So, first of all, I started out with a set of Jinja2 templates. I was
afraid I would have to make major changes to use them with Ersatz, but it
turned out I didn't need to change very much. However, I did notice a few
Jinja features that are not implemented in Ersatz:
1) {% endblock <block_name> %} causes a parser error. This is certainly the
least important of the issues I found.
2) The super() function is not supported. I was able to work around that
limitation but splitting up some blocks. Still, this would be a very
convenient feature to have.
3) {% if not loop.last %} also raises a parser error. This was a fairly
important feature for me, because I have a couple of templates that
generate JSON from arbitrary-length lists of objects, and there needs to be
a comma *between* the objects in JSON. So I was doing this:
{% for item in items %}
{
....
}
{% endfor %}{% if not loop.last %}, {% endif %}
My solution for Ersatz was to split the list in Scheme code into
all-but-last and last components, then do the following in my template:
{% for item in items %}
{
"a": {{ item.a }}
"b": {{ item.b }}
"c": {{ item.c }}
},
{% endfor %}
{
"a": {{ last_item.a }}
"b": {{ last_item.b }}
"c": {{ last_item.c }}
}
And actually, I have since realized a couple of things about this. One is
that I could split the list into car & cdr, and put a 'first_item' before
the 'for' block, with a comma in between. The other is that I need to
ensure that 'items' is not an empty list, otherwise there will be an extra
comma. Is this the way to do that?
Scheme:
models: `((items . ,(Tbool #f)) ...)
Template:
{% if items %}
{% for item in items %}
,
{
...
}
{% endfor %}
{% endif %}
FILTERS
My application uses several custom filters. I eventually figured out how to
do this, but it was rather challenging. First of all, the documentation
does not cover how to program a filter. For the record, I found that both
the inputs and outputs of user-defined filters need to be tvalues. I also
found that when I tried to use a filter that accepted one argument, Ersatz
was passing 2 arguments to it. I have no idea what the second argument is
supposed to be; I added some code to pretty-print the second argument to a
log file, and found that it was an empty list.
I'm wondering if you [Ivan] have any recommendations on the best way to
write user-defined filters. I can't use the 'func1-arg', 'func2-arg', etc.
procedures that you use for the built-in filters, since those procedures
are not exported. And in any case, I haven't read the code thoroughly
enough to really understand what they are doing.
I also found an error in the documentation related to filters. The
signature of the 'template-std-env' function says that the filters: keyword
argument should be a string list. In fact, it is a tvalue-alist of the same
form as the models: argument to 'from-file' and 'from-string'.
TVALUES
Now we come to a philosophical issue. To put it bluntly, I feel that the
tvalue datatype makes using Ersatz more complicated than it needs to be
without providing any huge benefit. By the way, I spent several years
programming in OCaml, so I know where this comes from. And, though in
general I liked the language and the community, and found the OCaml type
system very useful for certain kinds of applications, I was never convinced
that type safety was a panacea, and in particular I don't see it as very
useful for general web programming. Because in my limited experience, most
web applications don't do much complex data manipulation. Most of the
complexity is in (a) rendering pages; (b) validating user input; and (c)
database storage, search, and retrieval. The kinds of data structures that
an OCaml-ish type system helps with tend to be very short-lived. To give
one of many possible examples, let's say your application accepts
registrations of new users. Okay, so the user enters a username and
password and some other information in a form. If the form input is
accepted, then probably your app just puts it straight into the database,
reports to the user that their registration was successful, and moves on to
the next task. Now there are certain validation tasks that need to be done
before accepting the registration. For example, you need to ensure that the
username is unique. Well, that's just a matter of matching a string input
against existing strings in the database. Et cetera ... in any case, most
or all of the data in this type of interaction consists of strings; and
while they often need to be validated, that validation usually needs to be
done according to semantic constraints that are difficult or impossible to
describe in even a very sophisticated type system. I could go on, but
hopefully my point is clear, whether you agree with it or not.
As for what I think you should do, well, probably you should gather more
feedback on the merits of the tvalue datatype before considering any
changes. Maybe there are common use cases where this kind of type-safety is
really helpful, and maybe it makes the library easier to maintain. And I
plan to continue using Ersatz anyway, I just feel that the need to wrap
everything in tvalues makes it harder than it needs to be.
(NOT HAVING) IN-MEMORY TEMPLATE OBJECTS?
I have worked with several template systems in the past, and it seems like
most of them allow/require you to instantiate a template object in memory,
to which you can then feed variables and other contextual parameters before
rendering the output. Although this adds a bit of complexity to the user's
code, it seems like a good idea to me, because you can then reuse a
template by resetting it and passing it new variables. Though I have no
idea how much this affects performance vs. reading and parsing the template
from a string or file on each request, it surely makes some difference.
Though I have noticed there are several procedures for working with
template statements ... perhaps that addresses this usage in some way? I
don't really understand how that works yet.
THE NAME OF THE EGG
Okay, this is not entirely serious, but ... since, as I said, I was
thinking about creating a Jinja-like template library myself: the name
Ersatz doesn't make sense to me. I would like to call the egg Torii
(heh-heh). I've noticed that you [Ivan] are in Japan, so I am assuming you
know at least a little Japanese ... so maybe you can understand my little
pun there.
Anyway, thanks for everything, and goodbye for now.
--
Matt Gushee
_______________________________________________
Chicken-users mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/chicken-users