Agreed. I find the code in html.arc to be very confusing. When I look at it, I tend to feel either critical of the code or that pg must be way smarter than me.
I posted more-or-less the same question a few months ago [1]. From shader's comment there:
> By printing directly to stdout you don't get to have as many tail calls, but you don't have to worry about much complexity when it comes to aggregating output or adding new functionality. A simple pr is all it takes to add output, and you don't need to worry much about context.
This relates directly to your closing question:
> Wouldn't it be easier if everything just returned strings and then these strings were concatenated together?
If everything returns strings, it's nontrivial to make your functions composable. You can't stack them on top of each other unless they take strings as their arguments as well. But then you can only stack them on top of each other, not treat them as user-facing functions. So you have to keep at least a couple groups of functions with strictly different roles. Perhaps you've already thought of this.
I'm under the impression that html.arc is based on hacks that PG used while doing viaweb in common lisp.
If so, then this way of building html is probably a performance hack.
> If everything returns strings, it's nontrivial to make your functions composable.
Really?
> You can't stack them on top of each other unless they take strings as their arguments as well.
Exactly.
Isn't that how all html templating schemes work? A python example is Jinja macros[1]
Composing html elements as functions that take strings and return strings is the only way that makes sense to me.
> But then you can only stack them on top of each other, not treat them as user-facing functions. So you have to keep at least a couple groups of functions with strictly different roles. Perhaps you've already thought of this.
1. String operations are costly, printing to stdout is not. ie. if I need to run a function to generate some numbers, dumping them at the right time to stdout has very little overhead, having to weave them within a string operation costs so much more.
2. There's a benefit having a web server that pushes changes out to the browser incrementally via stdout. The user doesn't have to wait for the entire operation to complete to see the results. ie, what if the last half of your server operation, provides no output for half of your users?
3. Adding on to #2, for troubleshooting and iterative development purposes, it's nice to see a portion of the output within your browser to see how far a long your operation got, results wise, before it hit an error.
Sounds like a case of sacrificing expressiveness for performance. Somehow I think this goes against the design principles of arc.
For #2 and #3, the output has to be so huge before you reap this benefit. Most apps don't have this property, and if they did, I'd think there's a deeper design problem. Such problems can be better solved using asynchronous javascript requests (aka ajax).
> For #2 and #3, the output has to be so huge before you reap this benefit.
I think it's, more so, a case of how complex your code is rather than how big your output is. I can have 200,000 lines of code that outputs 20 small numbers. Knowing it hit the 8th number and what that number is can be huge for both a user and for development.
Also - maybe it's just me, but having partial output has helped me with 20 lines of code and very little output.
> Sounds like a case of sacrificing expressiveness for performance.
Also - maybe it's just me, but I've been spending most of my time with Clojure, where the Ring web server requires a string for an output. I found my code became less expressive than arc.
So, for Clojure, I actually wrote my own html framework to mimic arc's functions that write to stdout, then just put a big wrapper on it at the end: (with-out-str (println "stuff")). Next I plan to see if I can hack Ring@Jetty to pipe the output too.
Kinda funny, I went to Clojure and did the opposite of you. :)
So I gave it another whirl and here's my attempt to capture the essence of you're problem:
For example you would like to do this:
[1] arc> (spanclass "links"
(string "use this link:"
(tag (a href "http://mydomain.com/the-place-to-go") "here"))
And have it return this:
<span class="links">use this link:<a href="http://mydomain.com/the-place-to-go">here</a></span>
and your first attempt might be something like this:
[2] arc> (spanclass "links"
(pr:string "use this link:"
(tag (a href "http://mydomain.com/the-place-to-go")(pr "here")))
only you find it returns the wrong results:
<span class="links"><a href="http://mydomain.com/the-place-to-go">here</a>use this link:</span>
so now you're probably thinking by having functions return strings like this:
[3] arc> (tag (a href "http://mydomain.com/the-place-to-go") "here")
"<a href=\"http://mydomain.com/the-place-to-go\">here</a>"
then the original function [1] would have worked.
Instead, with arc, you need to approach your code differently. You need to think about the timing of when things are happening rather
than having available 'string-things' that you can compose by nesting your functions.
So with arc [1] needs to become [4]:
[4] arc> (spanclass "links"
(pr "use this link:")
(tag (a href "http://mydomain.com/the-place-to-go")(pr "here")))
<span class="links">use this link:<a href="http://mydomain.com/the-place-to-go">here</a></span>
Am I capturing it correctly? Does this answer your question on how I compose my functions?
I've re-looked into your original questions, in an attempt to provide a meaningful response, but I find the scenario's are not concrete enough.
For example I find the re-arrange function a little vague.
i.e. could you not:
(def something (a b c)
(output b c a))
Could you provide an real-case like example where you feel you can show a clear difference? For, I found, even your row example can easily work with stdout inside a function rather than using pg's macro. ie. Not liking how some of the existing functions/macros work doesn't mean string weaving is the answer.
And row is a pretty crappy example, even when I built my Clojure library, I ditched pg's implementation and went with a more useful implementation, yet it still uses stdout. You have to remember that pg only built those macro's to support his specific cases in his HN app.
Also, for > Too many tricks. Too clever.
Well it's a library, it's not expected you're crafting macros for your regular coding. I mean there's only so many HMTL cases you need to handle right? So if the library is complete, providing (macro's or not) succinct code and faster results, then it's probably good to have - tricks inside or not.
html.arc isn't that long, you could probably rewrite it using strings pretty quickly if you wanted to. And I'm sure that others using arc would be happy to have a string based system if you wrote it. There's no reason we can't have two alternate methods of generating html in arc.
Here's what I cooked up during the past coupla hours.
It doesn't do much, but builds a base for writing composable html elements.
It reuses the 'tag macro from html.arc as a base (no need to rewrite that part) but captures the output in a string (using 'tostring, of course). Thus the 'btag function becomes the base to build and compose html tags.
The few extra functions included serve as example of how to compose tags.
For example, 'hstack stacks its arguments horizontally using table columns.
'prn expressions are interspersed in between code blocks. They serve as examples, and I was using them for debugging.
(def listify (arg)
(if (acons arg) arg (list arg)))
(def btag (tagspec content)
"Low level tag function, takes two arguments: tagspec and content
content can be a list or an atom"
(let content (listify content)
(tostring (eval `(tag ,tagspec (pr ,@content))))))
(def element (tagspec . content)
"Simple element, just a convenience wrapper around btag"
(btag tagspec content))
; alias
(= e element)
(def section content
(btag 'div content))
(def inline content
(btag 'span content))
(prn (element 'div "Hello"))
(prn (element 'div "Hello " "World"))
(prn (element 'div "Hello" (element 'span "World")))
(prn (section "Hello" (inline "World")))
(def vstack args
"Stack things vertically"
(string:map section args))
(def hstack args
"Stack things horizontally"
(btag '(table cellspacing 0 cellpadding 0)
(btag 'tr
(map [btag 'td _] args))))
(prn "Testing vstack and hstack")
(prn (hstack "hstack" (section "hello") (inline "world")))
(prn (vstack "vstack" (section "hello") (inline "world")))
(def kls (classes . content)
"Generates a div with the classes given in 'classes'"
(let classes (string:intersperse " " (listify classes))
(btag `(div class ,classes) content)))
(prn (kls 'big "Hello " "world"))
(prn (kls '(small big) "Hello " "world"))