otherjoel / thenotepad

📓🍎An experimental blog written in Pollen / Racket
https://thenotepad.org
Other
77 stars 9 forks source link

Extending poly-branch-tag to allow default values for positional args #14

Closed jnboehm closed 2 years ago

jnboehm commented 2 years ago

Hi Joel,

thanks for describing the macro that allows to split pollen tag functions into more sensible (to me) logical units. I am currently using it and enjoy it quite a bit. However, I did notice one thing today, which is why I am writing to you. There currently is no way to specify a default value for positional arguments.

My current situation is as follows (I hope this illustrates the issue): I'd like to have a special tag function for linking to Wikipedia entries, named link-wiki. I've declared it as

(poly-branch-tag link-wiki url)

And usually write ◊link-wiki["url"]{URLs ...} in the source file. What often happens is that the link text and the URL are the same, i. e. ◊link-wiki["kerning"]{kerning}. What I'd love to have is an option of not specifying the URL, should the text also be the link target.

Unfortunately it does not work with default arguments either at the poly-branch-tag specification (as they'll turn into attributes) or at the definition of html-link-wiki (as those defaults will be overridden by the macro). I'd prefer to not turn it into a kwarg/attribute so that the notation stays simple and I don't have to specify #:url every time I need to set the URL explicitly.

I was wondering whether it's possible to specify default values not only for attributes, but also for the positional arguments. It seems that the notation for default arguments is a bit overloaded, so I am not sure if the current approach you have outlined can be easily adapted. I'd love to hear your thoughts on it, as I have to admit that my ability to write macros is essentially nil.

Thinking about it, maybe the notation for the macro could look like

(poly-branch-tag link-wiki url 'default (attr1 "default"))

if that can be implemented? Not entirely sure whether macros have information about whether the arg is an unbound variable or something else; my first guess would be no.

In any case, sorry for ambushing you with this and thanks again. If you don't feel like diving into this, please let me know, that is of course perfectly alright, too.

-- Cheers, Nik

PS: I was thinking about RSS feeds recently and then got your newsletter about splitflap a couple of days later, which made me happy and I hope I get take a look at it soon. Unfortunately I cannot watch the stream live as the timezone shift is too much but thank you for doing that!

PPS: I originally wanted to write you a mail to the mail address comments@thenotepad.org but the mail bounced, so there might be something wrong with the mail setup for this domain.

otherjoel commented 2 years ago

What I'd love to have is an option of not specifying the URL, should the text also be the link target.

I would just do:

(poly-branch-tag link-wiki)

(define (html-link-wiki attrs elems)
  ;; just use (car elems) for both the link text and the target
  ; …
)

I was wondering whether it's possible to specify default values not only for attributes, but also for the positional arguments.

Set the poly-branch-tag macro completely aside for the moment and consider the question as it applies to function calls in general. Think about these two different functions:

(define (simple a b c)
  ;; …
)

(define (variadic . args)
  ;; ...
)

It is simple to modify simple to have a default value for the first argument because you know exactly how many arguments to expect in advance. But with variadic it’s not so simple. It can accept any number of arguments, so how are you going to know whether the first argument was omitted?

All the functions defined define-tag-function (and by extension all the functions defined by poly-branch-tag) fall in the second case. If you look under the hood of the poly-branch-tag macro you’ll see that the “positional argument” is really just the first thing in the list of elements.

One thing you can do is define a “base” tag function using poly-tag-function and then put other functions in front of it that are more strict about their arguments:

(poly-branch-tag link-base url) ;; used like this: ◊link-base["http://example.com"]{link text}

(define (link-wiki [target "default"])  ;; used like this: ◊link-wiki{target}
  (link-base (xref->url target) target))
otherjoel commented 2 years ago

All this is not to say it's impossible to make a macro like poly-branch-tag that does what you want. But the heuristic that you use to determine whether some expected value was omitted from a variable-length list of arguments will vary a lot, so I don't think it would be productive for me to design a general use-case. It's best left as an exercise for the reader ;-) And Racket macros are very much worth learning because they are Racket's killer feature.

Also you might find it useful to check out this slightly newer iteration of poly-branch-tag. There I've split the large macro up into two much simpler ones: one for when keyword arguments are allowed and one for when they aren't.

jnboehm commented 2 years ago

Thanks for the thorough explanation! I was afraid that this wouldn't be straightforward, but that gives me a bit more to think about. An easy solution might be to check where there are two arguments at all, and if not just pass the first argument along as both; that would circumvent the issue of having to hack on the poly-branch-tag macro.

Thanks for pointing me to the newer version, I wasn't aware of that.

otherjoel commented 2 years ago

An easy solution might be to check where there are two arguments at all, and if not just pass the first argument along as both

Yes — this is what I was talking about with knowing how many arguments you expect (or care about). Just remember that it’s possible for the stuff in between { and } to consist of any number of elements. For example, if there are newlines or more tag functions in there, then you’ll get multiple elements. (See the docs for Scribble syntax, on which Pollen syntax is based.)

jnboehm commented 2 years ago

I adapted that solution and it works well for my use case; thanks! I'll close this and look at the new iteration of poly-branch-tag when I get the time.