mbutterick / pollen-users

please use https://forums.matthewbutterick.com/c/typesetting/ instead
https://forums.matthewbutterick.com/c/typesetting/
52 stars 0 forks source link

Implementing targets as separate components #11

Closed kdsch closed 4 years ago

kdsch commented 4 years ago

I'm learning Pollen to ditch Hugo and every static site generator that frustrates me. I am somewhat familiar with Scheme, but a newcomer to Racket, especially the module system.

I've done the fourth tutorial, on multiple output targets. I understand that pollen.rkt defines a module that resolves Pollen tags. The tutorial uses branching expressions within tag functions to implement different targets.

I want to make the implementation choice at the module level rather than in the tag functions. In other words: separate targets into different components. I think of a Pollen document as requiring an interface, with the current poly target controlling which implementation is used. However, I'm not skilled enough in Racket to do this.

mbutterick commented 4 years ago

Here’s one way to do it, putting each set of format-specific tag functions in its own source file, and then stitching everything together with a couple macros.

Here’s another way, using classes and inheritance.

Some have suggested that units are a possibility, but I don’t recall seeing a working example.

kdsch commented 4 years ago

Thanks @mbutterick!

otherjoel commented 4 years ago

For what it’s worth, some time ago I looked into using units for this purpose. The problems I bumped into were

  1. At the time I couldn’t determine how to conditionally call define-values/invoke-unit/infer since it is a top-level form [edit: maybe rather, module level?]. With a better understanding of macros, this may not be an obstacle anymore.

  2. More crucially, it doesn’t appear that (current-poly-target) will return anything but 'html at expansion time.

kdsch commented 4 years ago

@otherjoel Good to know.

I tried using units today. I found that define-values/invoke-unit/infer expects identifiers, which implies converting symbols (targets) into identifiers. This conversion would have to happen at run time, as the target varies at run time. I'm guessing this is a contradiction; I don't think identifiers can be created at run time. Whereas, macros can create them at compile time.

Recalling the notion of message passing I encountered in SICP, I experimented with that approach.

(define (target)
  (case (current-poly-target)
   [(html) html-target]
   [(txt)  txt-target]
   [else (error "unknown target:"
          (current-poly-target))]))

(define (heading . args) ((target) 'heading args))
(define (emph . args)    ((target) 'emph args))

(define (html-target method . args)
  (define (heading . elements)
    (txexpr 'h1 empty elements))

  (define (emph . elements)
    (txexpr 'strong empty elements))

  (case method
   [(heading) (heading args)]
   [(emph)    (emph args)]
   [else      (error "unknown method:" method)]))

This seems to have taken me a bit farther, but I'm now violating contracts in txexpr. Seems like a basic error, but I'm not sure what's going on, as the implementation didn't do this before.

txexpr: contract violation
  expected: txexpr-elements?
  given: '((("Karl Schultheisz")))

where resume.poly.pm contains

#lang pollen                                              

◊heading{Karl Schultheisz}                                

Today is ◊(get-date). I ◊emph{mostly} want this job.      
kdsch commented 4 years ago

The problem was too many variadic functions. After fixing that, the message-passing style more or less does what I want.