Fork me on GitHub
#beginners
<
2022-11-25
>
Madara Uchiha08:11:28

Yo, I have a hiccup expression like this

[:div
 (map-indexed (fn [i n] [:span (str "Task " i ": " n)]))]
This doesn't work because map-indexed returns a collection, rather than spread/flatten the result into the parent vector (duh) I saw that you can use (for for list comprehensions like this, but it doesn't look like it supports indexes very well. I could for over (map-indexed vector coll) but I imagine there's a more idiomatic way

Ferdinand Beyer08:11:42

There is no difference in using for or map/`map-indexed`: All of these return lazy sequences. I thought that this works for hiccup, as it supports and auto-flattens sequences? I could be mistaken though and only Reagent’s version supports this. The beauty of hiccup is that, like so often when programing Clojure: it’s just data. So you can just use code that produces the output you want. In your case, try into:

(into [:div] (map-indexed (fn [i n] [:span (str "Task " i ": " n)]) tasks))

Ferdinand Beyer08:11:42

This will append the sequence to [:div]

Madara Uchiha08:11:06

Thank you, last I tried just map/map-indexed I got an error about the array, but I might have misread it and it was something else, I'll try it again, it's more elegant.

👍 1
Ferdinand Beyer08:11:20

Finally when chaining transformations like this, I prefer using the ->> threading macro:

(->> tasks
     (map-indexed (fn [i n] [:span (str "Task " i ": " n)]))
     (into [:div]))

Madara Uchiha08:11:37

Yeah it doesn't work, because it thinks it's a component

Madara Uchiha08:11:06

[foo bar] component foo, argument bar [[foo bar]] component [foo bar] no arguments, since [foo bar] isn't a real component, it throws in runtime

Madara Uchiha08:11:25

I'll go with the into approach, I like it better than the (for, thanks 😄

Ferdinand Beyer08:11:39

Looks like you are using reagent?

Madara Uchiha08:11:56

Sorry, I missed that I didn't actually say that 😄

Ferdinand Beyer08:11:01

Then you should not need any of this. Just make sure to add a key to every dynamic element

Ferdinand Beyer08:11:56

E.g. this should work:

[:div
 (map-indexed (fn [i n]
               ^{:key i} [:span (str "Task " i)])
              tasks)]

Madara Uchiha08:11:06

I get the following warning

Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
and nothing is rendered

Ferdinand Beyer08:11:12

This looks like a different problem outside of this snippet. Are you maybe calling a hiccup function with () instead of building vectors with []?

Madara Uchiha08:11:47

Oh, I did something and now it works... I'm guessing some sort of paren order with Calva or something screwed it up

👍 1
Martin Půda09:11:23

This code returns the transducer- are you sure that you're calling map-indexed with two arguments?

[:div
 (map-indexed (fn [i n] [:span (str "Task " i ": " n)]))]

🙌 2
Madara Uchiha09:11:43

Yeah, I think the collection was one bracket outside too many

Ferdinand Beyer09:11:04

Good spotting, I somehow auto-corrected and added the tasks in my examples ;D

Hermann09:11:23

I didn't read the whole thread, but any collection should work. Did you arrive at the point yet where you realised the original example missed the collection argument?

Hermann09:11:06

Linke (map-indexed fn collection)

Madara Uchiha09:11:43

I see this is a common pain point lol

dumrat10:11:54

Is there a common way to pretty print hiccup output? Just for inspection at the REPL

Madara Uchiha10:11:07

I'd start with (cljs.pprint/pprint structure)

Madara Uchiha10:11:20

=> (cljs.pprint/pprint [:div {:style {:display    "flex"}
                           :overflow-y "auto"
                           :height "100vw"}
                     [:table {:border 0
                              :style  {:border-collapse "collapse"
                                       :flex-shrink     0}}]])
[:div
 {:style {:display "flex"}, :overflow-y "auto", :height "100vw"}
 [:table
  {:border 0, :style {:border-collapse "collapse", :flex-shrink 0}}]]
Seems to do a decent job of it

dumrat10:11:59

@U24MDANHY I mean the resultant string from a call to hiccup.core/html for example. The str returned is in a single line.

Madara Uchiha10:11:01

If you're on clojure and not clojurescript, it uses clojure-lsp under the hood

Madara Uchiha10:11:39

You got me there 😅

😂 1
dumrat10:11:18

No harm done. Perhaps I should send it to browser and inspect there but REPL is faster

kennytilton16:11:53

You should be able to parse the resulting HTML string using sth like https://github.com/clj-commons/hickory. "HTML can be parsed into hiccup vectors, or into a map-based DOM-like format very similar to that used by clojure.xml. "

👍 1
kennytilton16:11:53

More fun might be a quick hack to add line breaks in front of opening tags, identified with clever regex, and even identifying depth to be used for indentation. That way you can see the raw string, just formatted. Bonus points for figuring out cl-format's arcane syntax and doing it all in ten lines. :)

Madara Uchiha16:11:18

Don't make me post the obligatory html with regex link

🙀 1
skylize13:11:45

Regex may not be able to parse html, but that does not mean it cannot parse an html tag. Clojure with a sprinkle of regex is certainly powerful enough to get the job done correctly, especially if you know the source is well-formed because it was generated.

slk50011:11:49

(seq? '(1)) ; => true (if (seq '(1)) true false) ; => true (seq? '()) ; => true (if (seq '()) true false) ; => false I find it confusing. If seq? on empty list gives true, then creating a seq from empty list should be possible. What do you think?

delaguardo12:11:46

what do you think seq should return for empty list?

👍 1
slk50012:11:27

empty sequence

delaguardo12:11:58

seq creates an object that satisfy ISeq interface. what is empty sequence in this constrains?

Madara Uchiha12:11:56

It's been a hot debate in Lisp circles about the equivalence of nil and the empty list () and whether the empty list should test truthy or falsey etc. It's a deliberate design decision of the language, it was decided that an empty seq would be nil instead of the empty list.

👍 1
Madara Uchiha13:11:29

=> (seq '())
nil

slk50013:11:58

@U04V4KLKC 'seq creates an object that satisfy ISeq interface.' So why (seq? '()) ; => true It doesn't satisfy ISeq interface

Madara Uchiha13:11:36

(seq? (seq '()))
false

Madara Uchiha13:11:00

Which is pretty funny, tbh

slk50013:11:35

omg you complicated it much further 😛

slk50013:11:54

so if (seq? '()) would be false it would be more logical

delaguardo13:11:07

how? list implements ISeq. it doesn't matter if it is empty or not

Madara Uchiha13:11:22

It does seem a bit arbitrary/confusing that (seq '()) should return nil though, instead of the empty list. But on the other hand, I also know that the empty list is itself a pretty special/confusing value

delaguardo14:11:25

@UPBB20W20 (seq? '()) isn't false because empty list still the list which implement ISeq. looks like the main point of confusion is that seq and seq? functions somehow connected but in fact those are two independent things that share only one in common - they both use ISeq interface somewhere in implementation.

slk50015:11:53

thank you @U04V4KLKC @U24MDANHY for explanation 😄

slk50016:11:20

I have found example on 4clojure https://4clojure.oxal.org/#/problem/22 but to not use 'count' so using (seq x) in that way is kind of confusing for me, but probably it should not be used in that way, always better to use 'count' (#(loop [x % c 0] (if (seq x) (recur (rest x) (inc c)) c))'(1 2 3 4 5))

Madara Uchiha16:11:55

(rest sequence) accepts a sequence, and returns a sequence of the remaining elements, or nil , if there are no elements remaining - generally speaking, you should be asking (if (seq? x) rather than (if (seq x)

delaguardo16:11:29

I could disagree, it is advisable in clojure to use ideom (seq x) to check when collection is not empty. See docstring for empty? https://clojuredocs.org/clojure.core/empty_q

👀 1
👍 1
skylize18:11:08

The idiom of using (seq foo) as a test expression only works because seq returns nil for an empty list. nil is a falsy predicate value. A list is still truthy, even if it is empty.

(boolean nil)      ; false
(boolean (seq '()) ; false
(boolean '())      ; true
(defn reverse [m]
  (loop [m1 m m2 nil]
    ; If seq returned '(), this would be infinite
    ; loop, but emptiness is signaled by nil.
    (if (seq m1)  
      (recur (rest m1)
             (cons (first m1) m2))
      m2)))

slk50019:11:28

Please use the idiom (seq x) rather than (not (empty? x))

slk50019:11:11

I agree, (not (empty? x)) is much more readable then (seq x)

💯 1
robertfw19:11:48

The fewer things that need to be remembered, the better. I want my brain to be doing as little mental compilation/translation as possible when reading code.

👍 1
seancorfield20:11:06

(seq? coll) is a very specific test and a lot of things that are loosely non-empty sequences will return false for seq? @U24MDANHY A lot of people are surprised by the difference between seq? and sequential? and seqable?

👍 1
kennytilton22:11:47

I always slow down when I get into these obscure nuances with nil?, seq, blank, empty, yadda yadda. Common Lisp is cleaner here, but still can confuse. An empty list, eg, is a cons, a structure, with two properties, car and cdr, both of which have the value nil. But it is a structure, with object identity! And thus it is truthy! So we do not intiate a property with '() or (list), we just say nil. Then (when my-list ...) Just Works(tm), and it means (when {my-list is populated]...)`. ie we do not distinguish an empty list from "this app never even decide a list should exist".