13 Programming Pollen
Core techniques for getting Pollen to do your bidding.
I’ll assume you’re familiar with the workings of Racket generally and Pollen in particular, especially Pollen command syntax.
Experienced programmers, you will find this an easy read.
Inexperienced programmers, you might find some parts a trudge, but keep going. Learning the material on this page will pay hefty dividends for as long as you use Pollen.
13.1 Tag functions
The third tutorial introduced you to the idea that Tags are functions. Let’s recap the golden rules of tag functions that were first covered there:
Every Pollen tag calls a function with the same name.
The input values for that function are the attributes and content of the tag.
The whole tag — tag name, attributes, and content — is replaced with the return value of the called function.
13.1.1 Tag-function syntax
To be safe, let’s review what we mean by tag, attributes, and content, in Pollen-mode syntax:
A tag alone: ◊get-author-name[] A tag with content: Adding ◊em{emphasis to words}. A tag with attributes and content: ◊div['((attr1 "val1")(attr2 "val2"))]{My nice div.} A tag with attributes and content (alternate syntax): ◊div[#:attr1 "val1" #:attr2 "val2"]{My nice div.}
Let’s also recall that these commands can be written in Racket mode equivalently:
A tag alone: ◊(get-author-name) A tag with content: Adding ◊(em "emphasis to words"). A tag with attributes and content: ◊(div '((attr1 "val1")(attr2 "val2")) "My nice div.")
Let’s also remember that a tag without attributes or content is interpreted as a value. If that’s what you want — for instance, when you define a tag to hold a value — then great. But if you define a tag as a function, you need to add square brackets to signal that you want to evaluate the function:
◊(define author-name "Brennan Huff") ; a tag holding a value ◊(define (get-author-name) "Dale Doback") ; a tag function returning a value To refer to the value held by a tag name: ◊author-name or ◊|author-name| If a tag name represents a function, you need to add square brackets to invoke the function: ◊get-author-name[] This refers to the function as a value (not usually what you want): ◊get-author-name
13.1.2 Point of no return
If you’ve written functions in other programming languages, you might be accustomed to using a return statement to send a value back from the function. This doesn’t exist in Pollen or Racket — the return value of any function is just the last expression evaluated. In the example below, "BAP" becomes the return value because it’s in the last position, and "BOOM" is ignored:
13.1.3 Multiple input values & rest arguments
Sometimes a tag will have only one word or string that becomes its input. More likely, however, it will have multiple values (this is inevitable with nested tags, because the results aren’t concatenated). For instance, if we attach our function to em rather than strong:
Look what happens:
em: arity mismatch;
the expected number of arguments does not match the given number
expected: 1
given: 3
The error arises because the em function is getting three arguments — "RacketCon " '(strong "this") " year" — but has been defined to only accept one argument, word. This is the “arity mismatch.”
To fix this, it’s better to get in the habit of writing tag functions that accept an indefinite number of input values. You do this by defining your function with a rest argument (as in, “give me the rest of the input values.”) To use a rest argument, put it last in your list of input arguments, and add a period . before:
This time, the source file will run without an error, producing this:
'(root "I want to attend " "BOOM" ".")
A rest argument like parts is a list of individual arguments. So if you want to unpack & process these arguments separately, you can use Racket’s extensive list-processing functions (see Pairs and Lists). Also see quasiquote below.
13.1.4 Returning an X-expression
Often, you won’t use a tag function to replace a whole tag with a string — you’ll replace it with a different tag, described by an X-expression, like so:
Which produces:
'(root "I want to attend " (big "BOOM") ".")
The quote mark ' before the X-expression signals to Racket that you want to use what follows as a literal value.
To build X-expressions that are more elaborate, you have two options.
First is quasiquote. Quasiquote works like quote, but starts with a backtick character `. What makes it “quasi” is that you can insert variables using the unquote operator, which is a comma , or merge a list of values with the unquote-splicing operator, which is a comma followed by an @ sign ,@.
Let’s adapt the example above to use quasiquote. Suppose we want to take the parts we get as input and put them inside a big tag. This is easy to notate with quasiquote and the unquote-splicing operator, because parts is a list:
Which produces this:
'(root "I want to attend " (big "RacketCon " (strong "this") " year") ".")
Of course you can also nest X-expressions in your return value:
The second option for building X-expressions is to use the txexpr: Tagged X-expressions library that’s included with Pollen (see those docs for more information).
13.1.5 Using variables within strings
The usual way is to use the format function:
(format "String with variable: ~a" variable-name)
See the docs for format and fprintf for your options.
Be careful if you’re working with integers and X-expressions — a raw integer is treated as a character code, not an integer string. Using format is essential:
> (->html '(div "A raw integer indicates a character code: " 42)) "<div>A raw integer indicates a character code: *</div>"
> (->html `(div "Use format to make it a string: " ,(format "~a" 42))) "<div>Use format to make it a string: 42</div>"
13.1.6 Parsing attributes
Detecting attributes in an argument list can be tricky because a) the tag may or may not have attributes, b) those attributes may be in standard or abbreviated syntax. For this reason, Pollen provides a define-tag-function macro (in the pollen/tag library) that you can use in custom tag functions to separate the attributes and elements:
This will move the elements inside the big tag, and attach the attributes to the extra tag:
'(root "I want to attend " (extra ((key "value")) (big "RacketCon")) ".")