How to have context-sensitive tag functions?
#179
Closed
opened 5 years ago by beelzebielsk
·
8 comments
Loading…
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-list
tag would introduce whatever I'd need to introduce for a list, and thei
tags contained within would set up a single list item. However thei
tags of theequation-list
would format equations, which means doing different stuff than thei
tags of theplain-list
.A similar example comes from might come from a resume:
Here, I'd have the
name
tag inpersonal-information
create very large text for my document at the top, like a title. However, for thename
tag inside ofeducation-information
, I'd probably just make the tag bold.The way I've accomplished this so far is to either use
select
often or create definitions for the context-sensitive tags inside of the functions of the parents, likepersonal-information
andeducation-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:
Where
with-context
is some function or macro or whatever that would allow me redefine whati
means for all children of theequation-list
tag.How would I create
with-context
without 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 mypollen.rkt
file? 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.
Sematically,
i
just 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
i
in a plain list andi
in this equation list is the transformation I'd have to make for the output format. That detail should be hidden at the.pm
file level.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-context
inside of another tag created withwith-context
. For instance, if I had aplain-list
which contained at least oneequation-list
. In these cases, the nearest ancestor to the tag would decide which tag function gets used for the tag.i
was 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 usingwith-context
and otherwise fall back to a definition ofi
usingdefine-tag-function
.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:Result:
It is.
They do.
I’m not sure the
with-context
notation 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"pollen.rkt"
).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
i
would be changed for that function, but that wasn't the case. So, I gathered that the functions were being taken from the top-level ofpollen.rkt
and thus doing whatever I did would be meaningless.From looking at the names of the functions/macros here, it sounds like:
i
a macro which can expand to various names, and the name that will expand to will change over time.i
expands 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
doc
x-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 (
equation-list
,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:
doc
x-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: Wherea
,b
, andc
are 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, becauseequation-list
andplain-list
are macros. The stuff inside of equation-list and plain-list would still need to get expanded. I assume that, whensyntax-parameter
happens during compilation, whateveri
expands 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
doc
and exported.Your
let
example doesn’t work because the scope oflet
is syntactic. It only binds identifiers that are visible within the boundaries of thelet
expression. For instance, I assume this example doesn’t bother you:Of course, the
let
never even sees theop
on the outside — it’s evaluated away, and the result100
is what gets passed as an argument tof
.Nothing changes when we use
define-tag-function
, because under the hood, it just usesdefine
to create another run-time function:If we want
f
to be able to perform syntactic operations with the actual code that’s inside the various invocations off
, then we need to make it a macro. Then we can bring that code into the syntactic scope of ourlet
:“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 seeop
, it stops working:That’s because the outside
op
holds onto its binding as it passes through the macro, despite thelet
that wants to rebind it (because of hygiene).So this is where syntax parameters come in. We bring
op
into compile time by making it a syntax parameter, and then usesyntax-parameterize
to rebind it:Notice that this works even though we don’t know exactly where
op
occurs inside ofEXPR
.The wrinkle in your proposed
with-context
is that it wants the identifier bound todefine
to have one behavior at compile time (= rebindi
within 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 ofdefine
withinwith-context
.I'll take your comment about
with-context
under 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
decode
in all ways (I assume thatdecode
is 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-list
tag: I'd like each item to either be a single line's worth of strings, ori
tags. For instance:Should become:
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
i
tag, but otherwise not.If I immediately transformed my
i
tags 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:
math
which 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 nestedmath
tags (which does end up happening for reasons of short-term convenience resulting in long-term headache) I'd need to flatten thosemath
tags into one, since the nested math tag would escape LaTeX's math mode when it shouldn't.PS: If you're wondering why nested
math
tags 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 amath
tag, 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
decode
inside theequation-list
tag 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.