How to have context-sensitive tag functions? #179

Closed
opened 6 years ago by beelzebielsk · 8 comments
beelzebielsk commented 6 years ago (Migrated from github.com)

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:

◊equation-list{
    ◊i{x = x}
    ◊i{x \= x}
}

◊plain-list{
    ◊i{Whatever's up there is just bonkers.} 
    ◊i{But I know the truth.} 
}

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 the i tags contained within would set up a single list item. However the i tags of the equation-list would format equations, which means doing different stuff than the i tags of the plain-list.

A similar example comes from might come from a resume:

◊personal-information{
    ◊name{beelzebielsk}
    ◊email{...}
}

◊education-information{
    ◊school{
        ◊name{Collegiate College of the Elite Masses}
        ◊location{Above the clouds, where infinite skills create
        miralces}
    }
}

Here, I'd have the name tag in personal-information create very large text for my document at the top, like a title. However, for the name tag inside of education-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, like personal-information and education-information, and then call the tag function myself on the xexprs in my own code. For example:

(define-tag-function (equation-list attrs elements)
  (let* [(i (lambda (tag) 
              (format "\\item ~a" (car (get-elements tag)))))
         (formatted-items 
           (map i (findf*-txexpr tag (is-tag? 'item))))]
    (latex-environment 'itemize (string-join formatted-items "\\n"))))

But, that's not really what I want. I'd want something like:

(with-context [
  (i (lambda content 
       (format "$~a$" (string-join (map ~a content) " "))))]
  (define (equation-list . content)
    (let [(line-break "\\\\\n")]
      (latex-environment 
        'many-equations 
        (string-join content line-break)))))

Where with-context is some function or macro or whatever that would allow me redefine what i means for all children of the equation-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 my pollen.rkt file? Is there some way for me to leverage an existing part of pollen to create this behavior?

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: ``` ◊equation-list{ ◊i{x = x} ◊i{x \= x} } ◊plain-list{ ◊i{Whatever's up there is just bonkers.} ◊i{But I know the truth.} } ``` 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 the `i` tags contained within would set up a single list item. However the `i` tags of the `equation-list` would format equations, which means doing different stuff than the `i` tags of the `plain-list`. A similar example comes from might come from a resume: ``` ◊personal-information{ ◊name{beelzebielsk} ◊email{...} } ◊education-information{ ◊school{ ◊name{Collegiate College of the Elite Masses} ◊location{Above the clouds, where infinite skills create miralces} } } ``` Here, I'd have the `name` tag in `personal-information` create very large text for my document at the top, like a title. However, for the `name` tag inside of `education-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, like `personal-information` and `education-information`, and then call the tag function myself on the xexprs in my own code. For example: ``` (define-tag-function (equation-list attrs elements) (let* [(i (lambda (tag) (format "\\item ~a" (car (get-elements tag))))) (formatted-items (map i (findf*-txexpr tag (is-tag? 'item))))] (latex-environment 'itemize (string-join formatted-items "\\n")))) ``` But, that's not really what I want. I'd want something like: ``` (with-context [ (i (lambda content (format "$~a$" (string-join (map ~a content) " "))))] (define (equation-list . content) (let [(line-break "\\\\\n")] (latex-environment 'many-equations (string-join content line-break))))) ``` Where `with-context` is some function or macro or whatever that would allow me redefine what `i` means for all children of the `equation-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 my `pollen.rkt` file? Is there some way for me to leverage an existing part of pollen to create this behavior?
mbutterick commented 6 years ago (Migrated from github.com)

Before I answer: why is it important to overload i? Why not just give the list-item functions different names?

Before I answer: why is it important to overload `i`? Why not just give the list-item functions different names?
beelzebielsk commented 6 years ago (Migrated from github.com)

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 and i 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.

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 and `i` 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.
beelzebielsk commented 6 years ago (Migrated from github.com)

I'd like for this to work as "cleanly" as possible. So, keep the following in mind:

  • I'd like this to be composable. I should be able to use tags created with with-context inside of another tag created with with-context. For instance, if I had a plain-list which 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.
  • I'd like for these redefinitions of the tag functions to shadow any existing definitions of the tag function. For instance, if there were more than two lists to consider, and the transformation of 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 using with-context and otherwise fall back to a definition of i using define-tag-function.
  • I'd like to produce (if necessary) more than one tag function from 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.

I'd like for this to work as "cleanly" as possible. So, keep the following in mind: - I'd like this to be composable. I should be able to use tags created with `with-context` inside of another tag created with `with-context`. For instance, if I had a `plain-list` which 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. - I'd like for these redefinitions of the tag functions to shadow any existing definitions of the tag function. For instance, if there were more than two lists to consider, and the transformation of `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 using `with-context` and otherwise fall back to a definition of `i` using `define-tag-function`. - I'd like to produce (if necessary) more than one tag function from `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.
mbutterick commented 6 years ago (Migrated from github.com)

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:

;; pollen.rkt
#lang racket
(provide (all-defined-out))
(require racket/stxparam pollen/tag)

(define default-i (default-tag-function 'default-i))

(define-syntax-parameter i (make-rename-transformer #'default-i))

(define eq-list-item (default-tag-function 'eq-list-item))
(define eq-list-container (default-tag-function 'eq-list-container))

(define-syntax-rule (equation-list . XS)
  (syntax-parameterize ([i (make-rename-transformer #'eq-list-item)])
    (eq-list-container . XS)))

(define plain-list-item (default-tag-function 'plain-list-item))
(define plain-list-container (default-tag-function 'plain-list-container))

(define-syntax-rule (plain-list . XS)
  (syntax-parameterize ([i (make-rename-transformer #'plain-list-item)])
    (plain-list-container . XS)))
;; test.html.pm
#lang pollen

◊i{foo bar}

◊equation-list{
 ◊i{x = x}

 ◊plain-list{
  ◊i{Whatever's up there is just bonkers.} 
  ◊i{But I know the truth.} 
 }
    
 ◊i{x \= x}
}

Result:

'(root
  (default-i "foo bar")
  "\n"
  "\n"
  (eq-list-container
   (eq-list-item "x = x")
   "\n"
   "\n"
   (plain-list-container
    (plain-list-item "Whatever's up there is just bonkers.")
    "\n"
    (plain-list-item "But I know the truth."))
   "\n"
   "\n"
   (eq-list-item "x \\= x"))
  "\n"
  "\n")

I'd like this to be composable.

It is.

I'd like for these redefinitions of the tag functions to shadow any existing definitions of the tag function.

They do.

I'd like to produce (if necessary) more than one tag function

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").

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: ```racket ;; pollen.rkt #lang racket (provide (all-defined-out)) (require racket/stxparam pollen/tag) (define default-i (default-tag-function 'default-i)) (define-syntax-parameter i (make-rename-transformer #'default-i)) (define eq-list-item (default-tag-function 'eq-list-item)) (define eq-list-container (default-tag-function 'eq-list-container)) (define-syntax-rule (equation-list . XS) (syntax-parameterize ([i (make-rename-transformer #'eq-list-item)]) (eq-list-container . XS))) (define plain-list-item (default-tag-function 'plain-list-item)) (define plain-list-container (default-tag-function 'plain-list-container)) (define-syntax-rule (plain-list . XS) (syntax-parameterize ([i (make-rename-transformer #'plain-list-item)]) (plain-list-container . XS))) ``` ```racket ;; test.html.pm #lang pollen ◊i{foo bar} ◊equation-list{ ◊i{x = x} ◊plain-list{ ◊i{Whatever's up there is just bonkers.} ◊i{But I know the truth.} } ◊i{x \= x} } ``` Result: ```racket '(root (default-i "foo bar") "\n" "\n" (eq-list-container (eq-list-item "x = x") "\n" "\n" (plain-list-container (plain-list-item "Whatever's up there is just bonkers.") "\n" (plain-list-item "But I know the truth.")) "\n" "\n" (eq-list-item "x \\= x")) "\n" "\n") ``` > I'd like this to be composable. It is. > I'd like for these redefinitions of the tag functions to shadow any existing definitions of the tag function. They do. > I'd like to produce (if necessary) more than one tag function 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"`).
beelzebielsk commented 6 years ago (Migrated from github.com)

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:

(let [(i (lambda ...))]
   (define-tag-function (plain-list ...) ...))

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 of pollen.rkt and thus doing whatever I did would be meaningless.

From looking at the names of the functions/macros here, it sounds like:

  • You made i a macro which can expand to various names, and the name that will expand to will change over time.
  • The tag functions are now macros, and that part of their macro definition is to change what 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:

  • If an element is not an x-expession, return it.
  • If an element is an x-expression, traverse+transform it's children first, then transform the current x-expression with it's children replaced by the transformed children.

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:

  • It doesn't traverse through the 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
    ('a ('b "This is b") ('c "This is c")) 
    
    To:
    (a (b "This is b") (c "This is c"))
    
    Where a, b, and c are procedures in the second statement. And the final result is just what you get when you evaluate this expression.
  • When the macro tags are placed in this... transformation of doc, they change how any syntax objects inside of them are transformed. So something like:
    ('root ('equation-list ('i "x = x")) 
           ('plain list ('i "So plain.")))
    
    Would get expanded to:
    (root (equation-list ('i "x = x"))
          (plain-list ('i "So plain.")))
    
    Which is not quite the final form, because equation-list and plain-list are macros. The stuff inside of equation-list and plain-list would still need to get expanded. I assume that, when syntax-parameter happens during compilation, whatever i expands to will change. So we'd get:
    (root (equation-list-container (equation-item "x = x"))
          (plain-list ('i "So plain.")))
    
    Then, when we move onto plain-list:
    (root (equation-list-container (equation-item "x = x"))
          (plain-list-container (plain-item "So plain.")))
    

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.)

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: ``` (let [(i (lambda ...))] (define-tag-function (plain-list ...) ...)) ``` 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 of `pollen.rkt` and thus doing whatever I did would be meaningless. From looking at the names of the functions/macros here, it sounds like: - You made `i` a macro which can expand to various names, and the name that will expand to will change over time. - The tag functions are now macros, and that part of their macro definition is to change what `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: - If an element is not an x-expession, return it. - If an element is an x-expression, traverse+transform it's children first, then transform the current x-expression with it's children replaced by the transformed children. 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: - It doesn't traverse through the `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 ``` ('a ('b "This is b") ('c "This is c")) ``` To: ``` (a (b "This is b") (c "This is c")) ``` Where `a`, `b`, and `c` are procedures in the second statement. And the final result is just what you get when you evaluate this expression. - When the macro tags are placed in this... transformation of `doc`, they change how any syntax objects inside of them are transformed. So something like: ``` ('root ('equation-list ('i "x = x")) ('plain list ('i "So plain."))) ``` Would get expanded to: ``` (root (equation-list ('i "x = x")) (plain-list ('i "So plain."))) ``` Which is not quite the final form, because `equation-list` and `plain-list` are macros. The stuff inside of equation-list and plain-list would still need to get expanded. I assume that, when `syntax-parameter` happens during compilation, whatever `i` expands to will change. So we'd get: ``` (root (equation-list-container (equation-item "x = x")) (plain-list ('i "So plain."))) ``` Then, when we move onto plain-list: ``` (root (equation-list-container (equation-item "x = x")) (plain-list-container (plain-item "So plain."))) ``` 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.)
mbutterick commented 6 years ago (Migrated from github.com)

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 of let is syntactic. It only binds identifiers that are visible within the boundaries of the let expression. For instance, I assume this example doesn’t bother you:

#lang racket

(define (f x)
  (let ([op +])
    x))

(define op *)
(f (op 10 10)) ; 100 not 20

Of course, the let never even sees the op on the outside — it’s evaluated away, and the result 100 is what gets passed as an argument to f.

Nothing changes when we use define-tag-function, because under the hood, it just uses define to create another run-time function:

#lang racket
(require pollen/tag)

(define-tag-function (f attrs elems)
  (let ([op +])
    (first elems)))

(define op *)
(f (op 10 10)) ; 100 not 20

If we want f to 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 let:

#lang racket

(define-syntax-rule (f (op ARG ...))
  (let ([new-op +])
    (new-op ARG ...)))

(define op *)
(f (op 10 10)) ; 20 not 100

“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:

#lang racket

(define-syntax-rule (f EXPR)
  (let ([op +])
    EXPR))

(define op *)
(f (op 10 10)) ; 100 not 20

That’s because the outside op holds onto its binding as it passes through the macro, despite the let 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 use syntax-parameterize to rebind it:

#lang racket
(require racket/stxparam)

(define-syntax-rule (f EXPR)
  (syntax-parameterize ([op (make-rename-transformer #'+)])
    EXPR))

(define-syntax-parameter op (make-rename-transformer #'*))
(op 10 10) ; 100
(f (op 10 10)) ; 20 not 100
(f (+ (op 10 10) (op 10 10))) ; 40 not 200

Notice that this works even though we don’t know exactly where op occurs inside of EXPR.

The wrinkle in your proposed with-context is that it wants the identifier bound to define to have one behavior at compile time (= rebind i 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 of define within with-context.

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 of `let` is syntactic. It only binds identifiers that are visible within the boundaries of the `let` expression. For instance, I assume this example doesn’t bother you: ```racket #lang racket (define (f x) (let ([op +]) x)) (define op *) (f (op 10 10)) ; 100 not 20 ``` Of course, the `let` never even sees the `op` on the outside — it’s evaluated away, and the result `100` is what gets passed as an argument to `f`. Nothing changes when we use `define-tag-function`, because under the hood, it just uses `define` to create another run-time function: ```racket #lang racket (require pollen/tag) (define-tag-function (f attrs elems) (let ([op +]) (first elems))) (define op *) (f (op 10 10)) ; 100 not 20 ``` If we want `f` to 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 `let`: ```racket #lang racket (define-syntax-rule (f (op ARG ...)) (let ([new-op +]) (new-op ARG ...))) (define op *) (f (op 10 10)) ; 20 not 100 ``` “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: ```racket #lang racket (define-syntax-rule (f EXPR) (let ([op +]) EXPR)) (define op *) (f (op 10 10)) ; 100 not 20 ``` That’s because the outside `op` holds onto its binding as it passes through the macro, despite the `let` that wants to rebind it (because of [hygiene](https://beautifulracket.com/explainer/hygiene.html)). So this is where syntax parameters come in. We bring `op` into compile time by making it a syntax parameter, and then use `syntax-parameterize` to rebind it: ```racket #lang racket (require racket/stxparam) (define-syntax-rule (f EXPR) (syntax-parameterize ([op (make-rename-transformer #'+)]) EXPR)) (define-syntax-parameter op (make-rename-transformer #'*)) (op 10 10) ; 100 (f (op 10 10)) ; 20 not 100 (f (+ (op 10 10) (op 10 10))) ; 40 not 200 ``` Notice that this works even though we don’t know exactly where `op` occurs inside of `EXPR`. The wrinkle in your proposed `with-context` is that it wants the identifier bound to `define` to have one behavior at compile time (= rebind `i` 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 of `define` within `with-context`.
beelzebielsk commented 6 years ago (Migrated from github.com)

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 that decode 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, or i tags. For instance:

    ◊equation-list{
    x = x
    ◊i{x \= 
            x} 
    }
    

    Should become:

    ('equation-list ('i "x = x") ('i "x \= x"))
    

    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:

    • I don't think I'd like to accomplish with a tag function, even though it'd probably be possible.
    • The machinery I had in mind to accomplish this postprocessing could solve this issue's problem, too.
    • An example: suppose I had a tag called 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 nested math tags (which does end up happening for reasons of short-term convenience resulting in long-term headache) I'd need to flatten those math 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 a math 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.

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 that `decode` 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, or `i` tags. For instance: ``` ◊equation-list{ x = x ◊i{x \= x} } ``` Should become: ``` ('equation-list ('i "x = x") ('i "x \= x")) ``` 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: - I don't think I'd like to accomplish with a tag function, even though it'd probably be possible. - The machinery I had in mind to accomplish this postprocessing could solve this issue's problem, too. - An example: suppose I had a tag called `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 nested `math` tags (which does end up happening for reasons of short-term convenience resulting in long-term headache) I'd need to flatten those `math` 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 a `math` 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.
mbutterick commented 6 years ago (Migrated from github.com)

An example would be an equation-list tag: I'd like each item to either be a single line's worth of strings, or i tags.

Sure, you could put a decode inside the equation-list tag function that could do that.

If I immediately transformed my i tags into everything needed for an equation … It sounds like a brittle solution.

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)

typeset in some output (LaTeX for me)

I recommend posting LaTeX-related questions on the Pollen mailing list — I don’t use Pollen with LaTeX but others do.

> An example would be an equation-list tag: I'd like each item to either be a single line's worth of strings, or i tags. Sure, you could put a `decode` inside the `equation-list` tag function that could do that. > If I immediately transformed my i tags into everything needed for an equation … It sounds like a brittle solution. 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) > typeset in some output (LaTeX for me) I recommend posting LaTeX-related questions on the [Pollen mailing list](https://groups.google.com/forum/#!forum/pollenpub) — I don’t use Pollen with LaTeX but others do.
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: mbutterick/pollen#179
Loading…
There is no content yet.