How to have context-sensitive tag functions?
Closedopened 5 years ago by beelzebielsk · 8 comments
Reference in New Issue
There is no content yet.
Delete Branch '%!s(<nil>)'
Deleting a branch is permanent. It CANNOT be undone. Continue?
In several documents that I've written with pollen, I've wanted to have tags whose meaning (the function corresponding to the tag) depends on the tag that they're contained within. Something like the following, for instance:
In a simple example like this, where I'd be outputting to TeX or something, the
plain-listtag would introduce whatever I'd need to introduce for a list, and the
itags contained within would set up a single list item. However the
itags of the
equation-listwould format equations, which means doing different stuff than the
itags of the
A similar example comes from might come from a resume:
Here, I'd have the
personal-informationcreate very large text for my document at the top, like a title. However, for the
nametag inside of
education-information, I'd probably just make the tag bold.
The way I've accomplished this so far is to either use
selectoften or create definitions for the context-sensitive tags inside of the functions of the parents, like
education-information, and then call the tag function myself on the xexprs in my own code. For example:
But, that's not really what I want. I'd want something like:
with-contextis some function or macro or whatever that would allow me redefine what
imeans for all children of the
How would I create
with-contextwithout re-writing part of the logic of pollen (finding each element, calling the appropriate tag function, replacing that element with the output of the tag function) in my
pollen.rktfile? Is there some way for me to leverage an existing part of pollen to create this behavior?
Before I answer: why is it important to overload
i? Why not just give the list-item functions different names?
Hm. I misspoke about the semantics. Sorry.
ijust means item to me. At the markup level, I don't care to think any further than that, so I don't want to change their names. Name changes at the semantic level should mean different meanings for the different tags. But they don't have different meanings.
What changes from
iin a plain list and
iin this equation list is the transformation I'd have to make for the output format. That detail should be hidden at the
So my problem is actually that tags with the same semantics may actually need different functions for transformation, based on their parent. Same meaning doesn't mean same appearance in the output everywhere the tag appears.
Thanks for helping me clarify that point.
I'd like for this to work as "cleanly" as possible. So, keep the following in mind:
with-contextinside of another tag created with
with-context. For instance, if I had a
plain-listwhich contained at least one
equation-list. In these cases, the nearest ancestor to the tag would decide which tag function gets used for the tag.
iwas pretty much the same for all the lists with the exception of one or two, then I'd like to define the one or two using
with-contextand otherwise fall back to a definition of
with-context. This goes back to the previous point, where there might be one or two exceptions for how to transform the content of a tag into the final content for presentation.
I'm willing to make the necessary changes myself, but I'd like to know where to start.
If you want to dynamically rebind identifiers at compile time, you probably want to use
syntax-parameterize, which requires converting your top-level list tags into macros:
I’m not sure the
with-contextnotation you suggest will work, because it straddles compile-time and run-time in a weird way. But having seen the pattern, you could imagine another way of compressing the notation (say, with a helper macro inside
Thanks. I definitely have some reading ahead of me to do. I'm a little new to racket (at least the macro side of it). I don't yet know enough to see how what I asked for must straddle compile-time and run-time.
I'm also a little curious to understand how/why this works. One of my first attempts to make this context stuff was to just do something like this:
I figured that the
iwould be changed for that function, but that wasn't the case. So, I gathered that the functions were being taken from the top-level of
pollen.rktand thus doing whatever I did would be meaningless.
From looking at the names of the functions/macros here, it sounds like:
ia macro which can expand to various names, and the name that will expand to will change over time.
iexpands to from the location in the program where the tag macro is invoked.
I thought that the general process that pollen follows is to traverse the
docx-expression and run the corresponding tag functions for an x-expression. I would boil down that traversal of the x-expression as:
But, if you do things this way, then the children would get transformed before those macro tags (
plain-list) are ever used. Which would make this stuff useless. Plus, macros aren't supposed to have effects during run-time.
The only way I can currently think that would make sense is that Pollen works a little differently than I first thought:
docx-expression during run-time. It transforms that x-expression into an s-expression of function calls, where the functions are the tag functions. So you'd go from something like To: Where
care procedures in the second statement. And the final result is just what you get when you evaluate this expression.
doc, they change how any syntax objects inside of them are transformed. So something like: Would get expanded to: Which is not quite the final form, because
plain-listare macros. The stuff inside of equation-list and plain-list would still need to get expanded. I assume that, when
syntax-parameterhappens during compilation, whatever
iexpands to will change. So we'd get: Then, when we move onto plain-list:
Is that roughly the case? (Also, thanks for writing Beautiful Racket. I'm not finished with it, but I'm less than 100% lost, which helps.)
I think you’ve basically got it. Pollen is just an alternate way of writing Racket programs. So everything that’s true about Racket’s evaluation model is true about Pollen’s (and then Pollen adds a few conveniences on top of that)
During compile time, the macro expander traverses the parse tree (= the S-expression beginning with
root) top to bottom, expanding macros and determining identifier bindings as it goes. (As you say, the
"pollen.rkt"is an implied source of bindings.)
During run time, the evaluator goes the opposite direction — bottom to top — turning S-expressions into values that become arguments to the next tag function, and so on. The result of this process gets stored in
letexample doesn’t work because the scope of
letis syntactic. It only binds identifiers that are visible within the boundaries of the
letexpression. For instance, I assume this example doesn’t bother you:
Of course, the
letnever even sees the
opon the outside — it’s evaluated away, and the result
100is what gets passed as an argument to
Nothing changes when we use
define-tag-function, because under the hood, it just uses
defineto create another run-time function:
If we want
fto be able to perform syntactic operations with the actual code that’s inside the various invocations of
f, then we need to make it a macro. Then we can bring that code into the syntactic scope of our
“Well, that’s cheating, because all we’ve done is ignore the original
op.” True. If we change the macro pattern so that we can’t see
op, it stops working:
That’s because the outside
opholds onto its binding as it passes through the macro, despite the
letthat wants to rebind it (because of hygiene).
So this is where syntax parameters come in. We bring
opinto compile time by making it a syntax parameter, and then use
syntax-parameterizeto rebind it:
Notice that this works even though we don’t know exactly where
opoccurs inside of
The wrinkle in your proposed
with-contextis that it wants the identifier bound to
defineto have one behavior at compile time (= rebind
iwithin the code underneath) and a different behavior at run time (= behave as an ordinary tag function). Both operations are possible, but you need to bind separate identifiers. To make your proposed notation work, you’d have to mess with the meaning of
I'll take your comment about
with-contextunder consideration. I'll close this issue, since you answered my question (thanks again!).
I might not use this technique, though. As I've continued to use Pollen, my concerns have changed a little bit. I'm considering solving this problem using something similar to pollen's decoder functions. This function should act just like
decodein all ways (I assume that
decodeis recursive), except that there's an option to pass some context value that is accessible to the user-supplied decoding procedures, and the user has the ability to control what values become the context value and when.
I'm considering this direction because, as I continue to use Pollen, I end up wanting to do a lot of processing that would require some contexual knowledge, like knowledge of siblings and parents. For instance:
I've wanted to keep the syntax for some tags pretty lax while allowing for precision when needed. An example would be an
equation-listtag: I'd like each item to either be a single line's worth of strings, or
itags. For instance:
There's times when an equation would render as something short, but is long and ugly to type out, but it's not that frequent a thing. In this case, I'd like to use an explicit
itag, but otherwise not.
If I immediately transformed my
itags into everything needed for an equation (which would reduce the equation to a large string) then the already transformed equation would end up getting picked up by whatever postprocessing procedure I'd write to isolate strings on their own lines as items. I could potentially try to distinguish the already transformed equations from those not, but that would require judging them diffrently based on the contents of the strings, and I don't like the idea of that. It sounds like a brittle solution.
I'm already finding other reasons to do postprocessing which require somewhat more complicated context where:
mathwhich would indicate math to be typeset in some output (LaTeX for me). That typesetting would mean producing a string surrounded by
$, since math in LaTeX is delimited by a pair of
$. If there were nested
mathtags (which does end up happening for reasons of short-term convenience resulting in long-term headache) I'd need to flatten those
mathtags into one, since the nested math tag would escape LaTeX's math mode when it shouldn't.
PS: If you're wondering why nested
mathtags might be a problem for me, I don't want to use the TeX-side solution for this, because it results in ugly TeX, and I know that there'll come a time when I have to make a few quick-and-dirty changes to the TeX. Also, I have tags I have which will always output typeset math, and I don't want to manually place them in a
mathtag, so I'd rather make that automatic. However, doing so naively results in nested math environments once I try combining these tags with other typeset math.
Sure, you could put a
equation-listtag function that could do that.
As a rule of thumb, it’s wise to process everything into one big X-expression and then go back and render it in one go (instead of interleaving tree processing & rendering, which as you say creates headaches)
I recommend posting LaTeX-related questions on the Pollen mailing list — I don’t use Pollen with LaTeX but others do.