Hey all! I'm working on a HTML parser/builder thing and I have a question about aesthetics / usability of naming things. Namely, I have a simple macro that makes it more convenient to create the data structure (nodes), but I'm not convinced about the name of it. I'm currently calling it a dollar sign macro $, i.e:
($ doctype {:html true})
($ head
($ meta {:charset "utf-8"})
($ link {:rel "stylesheet" :href "style.css"}))
($ body
($ span {:class "test"}
($ span {:class "test2"})))
What's giving me doubts about it is that while I am used to programming with a USA ANSI keyboard, so the dollar sign is conveniently located for me, I do know that there are people who program in their own language, and so for them it might be a euro sign there, or something else entirely. I'm also not sure what else to pick, since ideally it'd be one character, to make it as convenient as possible. Thoughts?I would not give up with Hiccup. You can try to force Hiccup to operate at compile time. For example like this:
(defmacro compiled-html [content]
(str (h/html content)))
(compiled-html [:span {:class "foo"} "bar"])
You can even try to mark static HTML. The quote sign would be good in my opinion. Try out this:
(defmacro html [content]
(if (and (= (type content) clojure.lang.Cons)
(= (first content) 'quote))
`(compiled-html ~(second content))
`(str (h/html ~content))))
(html '[:span {:class "foo"} "bar" (+ 3 3)]) ; static
=> "bar+33"
(html [:span {:class "foo"} "bar" (+ 3 3)]) ; dynamic
=> "bar6"
It seams to work.My 2 cents: a macro is beautiful indeed but a regular vector is more convenient. Vectors and map are good with conditions like cond-> and similar. Just think how you going to generate an HTML layout full of conditions.
Oh yeah, I completely forgot to mention that. It's a very nice consequence of "just data" indeed.
I mean, something like this: if user is authorized, than this block of HTML, otherwise another. If user role is admin, that this, if manager, than this, and on and on
I thought a premise for the original question was that it was pretty static code to be generated at compile time. But I completely agree; hiccup being "just data" makes it very flexible.
Well, plain "if-then" works with $ just fine:
($ :div
(if logged-in?
($ logged-in-panel)
($ anon-panel)))
But things like interpose or into - not sure.Well, apparently for also works just fine so it shouldn't be an issue.
Yeah I mean, before going too far with a macro, one should ensure it doesn't break regular stuff like cond->, for, into, etc
At compile time, the macro is not intrusive because it wraps only one html node, compared to hiccup/data which is not ideal for an "HTML layout full of conditions" . (and yes p-himik you don't even need to into [] everything π, and you also can (map #($ :li %) things) )
A simple (def my-html (str (h/html β¦)) will also enforce compile time generation. Then you can even call functions and things like map. And everything is still at compile time.
First things first - why does it have to be a macro? And why not Hiccup or something Hiccup-like?
I'm with @p-himik. Unless your not making a compiler along the lines of Svelte, you can't really beat hiccup on the aestetics side of things.
I agree on hiccup. Nice of you to think about different keyboard layouts π
uix uses $ so I think it's fine. I don't think there's something else better and unused by clojure. And if there is for one, they can make an alias.
I assume Asko does this so you can optimize stuff at compile time, borkdude/html does the same thing and never lets you generate runtime tags. but it lets you do this via compile time hiccup. This is inspired by squint which lets you have hiccup -> html or JSX without adding a runtime library. https://github.com/borkdude/html
Yup exactly, it's to run at compile time. It could also work as a function, of course, but then would come with a performance penalty. I'm writing this as a runtime-agnostic library so that it would work everywhere (cljs, clj, jank, babashka, etc).
The fact that there is a context where a macro is useful doesn't necessitate the syntax, or using that macro everywhere.
I know that it's not what you asked, but a very much justifiably common approach is to delegating things to macros as the very, very last step.
So pretty much like borkdude/html above.
Going the "macros first" route is much worse than relying on, say, β instead of $. At least in the case of β, users can create an alias. But nobody can reliably use a macro as a function.
I have no problem providing a function version with my library as well, if the macro is not desired by some, that way everybody can be happy π
Yeah, but then the syntax would probably also be different. And then you might land on Hiccup and just forget about this problem at all. :)
No the syntax would be the same, as per my testing at least.
It's the performance that degrades then
BTW another downside of macros like ($ body ...) is that the editor would have to be aware that $ is a macro and that body is just a symbol and not something that must be resolved. In some cases it's trivial, but not always.
> No the syntax would be the same, as per my testing at least.
$ can recursively transform the whole form - you don't have to nest ($ ...), it can be just at the top.
yep, that's what borkdude/html does basically
also weavejester/hiccup will do that btw
if you use hiccup inside the html macro
In other words, it's pretty much a solved problem. :) (Although I still sometimes reimplement a subset the same thing in my own code, usually because I need some bells and whistles.)
There are benefits to both $ and nested $, it depends on the API. Thatβs why uix made that choice over hiccup
If I only worked on problems that were not yet solved, I'd probably never work at all. I like making things, unique or not, doesn't matter to me. Nobody is forced to use the things I create, and are free to use alternatives that they like better.
In any case, I think I'll stay with $ since, as pointed out here, uix seems to use it as well, and so if it's a more-or-less known convention. I suppose if someone doesn't like it they can always alias it with something else.
I agree and if libraries werenβt created just because alternatives already existed... well, there wouldnβt be that many
It's a different argument though. Not making != not inventing a new syntax.
But what does $ offer in UIx that Hiccup-the-syntax with a top-level macro cannot handle?
you can't parse hiccup at compile time easily because you don't know when it starts and when it ends (with variables, props, etc)
you can read uix docs it is interesting
also converting clojure maps into JS objects at runtime for static properties is not ideal
you can read uix docs it is interestingI remember doing it a long time ago and ending up sticking with Hiccup. :D
UIx has $ as a boundary of sorts. If you want to embed Hiccup in Hiccup via a named reference or an invocation - just use a boundary as well, that's it. But it would require being a tiny bit more careful since that boundary is conditional and isn't required 100% of the time.
> also converting clojure maps into JS objects at runtime for static properties is not ideal
You can also do it inside a macro that consumes Hiccup.
sure, then ($ [:div]) instead of ($ :div). Actually it exists but is not used because of that EDIT: https://github.com/pitch-io/uix/blob/master/core/src/uix/dev.clj#L73-L87
Oh, cool - GitHub now has three scrollbars. We truly do live in the age of abundance.
> it exists but is not used because of that Not sure what "that" refers to.
to the extra syntax due to hiccup
btw here's a playground if you want to play around with squint html https://squint-cljs.github.io/squint/?src=KGRlZm4gbXktaHRtbC1mbiBbXQogICNodG1sIFs6ZGl2CiAgICAgICAgIFs6cCAiSGVsbG8iXV0pCgooc3RyIChteS1odG1sLWZuKSk%3D&repl=true
ho boy the editor is hard to use, would be better without intelligence
most emacs shortcuts should work I believe
@p-himik see the difference now? ($ ) is not much worse than []
I understood the difference from the get go. I just disagree on the "not much worse" part. :)
At best, it's comparable. On your screenshot, it's definitely worse IMO because of extra :. But not using : also has downsides that I mentioned, with editor awareness.
when your familiar with the syntax it's fine. I also loved hiccup before and didn't want to use uix because of that. Now I can't go back to reagent π
Of course, there's a decent amount of purely personal preference.
Like e.g. me being a Vim shortcut addict and a habitual replacer of Hiccup forms, replacing something like [:p subtitle] with [:div {:class :panel} something-else] would be a matter of navigating anywhere inside that form and starting with di[.
With ($ p subtitle), I either have to use a much longer di($ or navigate precisely after $ and kill forward. Muscle memory just flies out of the window.
There were other gripes I personally had with the ergonomics of ($ ...) back when I evaluated it for myself, but I wouldn't be able to recall.
((we have derived from original subject but @borkdude I don't know emacs shortcuts but I can't fix the code by closing the second function above. It adds the parenthesis at the end of the last line))
@p-himik yes but you are doing runtime hiccup. I won't be sure you would stick with hiccup if you had to encapsulate it with a macro each time you want to convert it (like in let, when, function returns, etc)
Can't tell for certain, but I doubt that. To me,
(defui app []
(let [[state set-state!] (uix.core/use-state 0)]
($ :<>
($ button {:on-click #(set-state! dec)} "-")
($ :span state)
($ button {:on-click #(set-state! inc)} "+"))))
is much, much noiser than
(defn app []
(let [[state set-state!] (uix.core/use-state 0)]
($ [:<>
[button {:on-click #(set-state! dec)} "-"]
[:span state]
[button {:on-click #(set-state! inc)} "+"]])))"noiser" like people who say () are noisy in LISP? π it's noiser because you aren't familiar with it It looks like more consistent and clean for me in the first form. Again as you said it's a POV. (and you have an example with only one $ here, not a real world component)
(I think I really hate the ($ [ syntax)
and if we could use hiccup as a direct remplacement of the $ macro, I would use it, I really like hiccup.
It's not about familiarity, it's about perceptual and cognitive load. I am familiar with it enough I'd say - I have tried it, albeit years ago.
And it's fine if different constructs result in different kinds and strength of cognitive load in different people.
Now I could get used to that cognitive load. But it would be load nonetheless.
> (I think I really hate the ($ [ syntax)
Could be #h [...]. Or anything like that.
And could actually be none of that at all if you use some hypothetical defui instead of defn that rewrites the whole form. But it would have to be aware of every something in (something ...) forms. Doable, just not pleasant.
yes with defuiI think about it quite a lot and with still some unresolved cases (can be resolved by using an explicit macro) it's definitely too intrusive for me (and source of bugs).
For the syntax #h would be better yes, but #h [:div] instead of just ($ :div) idk. When it's larger maybe hiccup becomes better but by consistency you have to make a choice. Also putting #h in every branch of the code feels wrong to me.
Actually it's funny because a few months ago I started a new library called rehiccup as a modern React replacement for reagent. In the end I had to admit than uix is really the most elegant way for me to write modern React in cljs these days, even though I was aware of it at that time and wanted to stick with Hiccup.
How would one implement a data structure with "pointers" (or equivalent) in Clojure? Concretely, I was trying to implement a doubly connected edge list yesterday (https://en.wikipedia.org/wiki/Doubly_connected_edge_list) One option is Java interop, of course. But is there a more clojureessque way?
OK yeah, if it's about the exercise of solving the problem that's a whole other deal
adj-list works great with immutability and makes a lot of graph algorithms straightforward, and most data structure stuff can conceptually be reduced to graph operations
so you can either write graph code that treats the adj-list as doubly linked, or write functions to manipulate double links in an adj-list, and that can be extrapolated to other operations for weird data structures
Advent of code 2022 day 20 had a doubly linked list. Check out some Clojure solutions to that
A pointer is just a memory address. So the same way but you also control what the memory is.
Using deftype
I still remember this solution by Arne: https://github.com/plexus/advent-of-code/blob/91e13881f60cc0da14ce5eca959552acebcd7685/src/advent2018/day9.clj#L17
(it was actually 2018 day 9)
"this data structure provides efficient manipulation" - a lot of immutable things have equivalent semantics (consider also an adjacency list based graph) but none of them fulfill the main requirement, which is to be a place to do fast manipulation of topology
if your main bottle neck is number crunching numbers in complex data structures, there's no getting around the fact that immutability is overhead
there's a Clojure implementation of Dancing Links by Mark Engelberg, but iirc it wraps a Java lib because he said nothing else he tried (protocols/records, atoms as pointers, etc.) was performant enough https://github.com/Engelberg/tarantella
@btowers793 Did he try deftype ?
I don't remember him mentioning it, it was in the talk which is linked in the readme
That's fair. It's possible it wasn't possible, because his is an algorithm, so my guess is he needs local pointer manipulation and mutation, not just class-level.
With deftype, you can have class-level mutable fields that can be type hinted to primitives. But the protocol methods you implement are still Clojure, they can just mutate the fields, but internally will still have immutable locals, make use of mostly immutable things, etc. And they will also be limited to primitive typing of functions I think, so still difficult to avoid full boxing and specialize to all types.
Thanks for all the ideas here π€© I haven't had time to come read through it all, but I will update when I have an implementation π > if your main bottle neck is number crunching numbers in complex data structures, there's no getting around the fact that immutability is overhead My main bottleneck is thinking differently when programming Clojure π