Fork me on GitHub
#beginners
<
2022-08-28
>
Ben Lieberman16:08:18

a little confused by the arity of recur. trying to figure out a little function to walk through some simple html but this (not complete yet) doesn't work:

(loop [head (:content (as-hickory parsed-doc))]
  (cond-> head
    (vector? head) (recur (rest head))
    (map? head)))
bc it says recur is getting two args? Is not rest head returning the tail of the seq as one thing?

Bob B16:08:31

Are you definitely meaning to use cond-> instead of cond? In this case, assuming head is a vector, it would get threaded into the recur form, meaning it would get two args.

Ben Lieberman16:08:06

yea I realized shortly after I posted that I needed cond instead of the cond->

Eric Honsey22:08:43

Downside of loop / recur is that it needs to be in a tail position within your expressions. So cond doesn’t help here and you need an ugly arrow structure via if

(loop [head (:content (as-hickory parsed-doc))]
  (if (vector? head)
    (recur (rest head))
    (if (map? head)
      ;; Replace with your recur form on a map data structure
      (recur (rest (map second head))))))

Ben Lieberman23:08:16

I guess something in clojure.walk would probably be helpful here

skylize01:08:10

Currently you are not recurring if it's a map. Does that case really need to be in the loop? Simplifies the logic if you can deal with that ahead of time. In the case of vec, rest is going to be an IndexedSeq that fails the vector? test. You'll generally be testing for seq? here instead. recur takes the same number of arguments as your loop , where loop is basically equivalent to using fn. You might grock the number of args question more easily if you start with a regular anonymous function instead of the more explicit loop.

(fn [param1 param2]
 (compute-stuff)
 (recur arg1 arg2)) ;; recur calls the surrounding anon function

(fn my-fn [param1 param2 param3]
 (compute-stuff)
 (recur arg1 arg2 arg3)) ;; recur is like calling my-fn, except it performs stack eliding

Ben Lieberman02:08:51

yeah that's my fault for omitting that part @U90R0EPHA... I was initially so confused by the very obvious error I was making with cond vs cond-> that I did not continue on writing out the body of the function. but thanks for your tips, that is good to know

Russ Olsen13:08:21

So a few things tangentially related to the original question: • When I have trouble working out why my loop/recur isn’t doing what I want, I will usually pull the loop out into its own function with a conditional recur at the end. • I also try to ask myself how deep the thing I’m trying to recur on is likely to be. So if I am just recursively descending an html file, ah maybe just doing a vanilla recursion is fine. If I’m doing (factorial 10000000) maybe not. • And finally, as a beginner I tended to think in terms of loops instead of things like map, reduce, dorun and doall.

Ben Lieberman15:08:25

ok so Programming Clojure convinced me I should stop using loop /`recur` and I came up with a much nicer solution using this transducer but how can I get it so that I don't have to do each step twice like here

(defn transduce-html [html] 1 reference
  (->> html
       (mapcat :content)
       (filter (fn [el] (if (string? el)
                          (not (string/blank? el))
                          (:content el))))
       (mapcat :content)
       (filter (fn [el] (if (string? el)
                          (not (string/blank? el))
                          (:content el))))))

(into [] (transduce-html head))
would it be simplest to just turn it into this
(defn transduce-html [html] 1 reference
  (->> html
       (mapcat :content)
       (filter (fn [el] (if (string? el)
                          (not (string/blank? el))
                          (:content el)))))
and call it twice?

skylize21:08:55

Definitely not a transducer. Note that using`into` with a transducer would be 3-arity (into to xform from), while you are (correctly) calling it with 2-arity (into to from), where from is the result of calling (transduce-html head). --- You can use let to define a local function to call as many times as you need.

(defn my-fn [x]
 (let [local-fn (fn [y] do-stuff)]
  (-> x do-stuff do-stuff do-stuff do-stuff)))
But it seems like you are trying to parse a DSL that translates to HTML. Because HTML and DSLs that translate to it are recursive tree structures, you probably want recursion for parsing. Passing the tree through your function twice, as done here, means you will get exactly 2 levels of the hierarchy, and no deeper. If that is sufficient for your use-case, then go for it.

Ben Lieberman01:08:27

What makes this not a transducer? Is it because of using ->>? I was under the impression that functions like mapcat and filter return transducers when they are not given a collection, which I guess I am here using ->> but only when I call the function right? If so what would make this into one? Removing the threading macro? I think the basics I understand but I'm definitely lost about the last part like using reduced? and eduction and all that

skylize01:08:11

But your are giving it a collection. You are not acually using a 1-arity version of mapcator filter, even though you only type the first argument. The threading macro rewrites your code to include in the collection as the final argument.

(->> html (mapcat :content) (filter predicate))
is rewritten by the macro into
(filter predicate (mapcat :content html))
which is equivalent to
(let [content (mapcat :content html)]
      (filter predicate content))

Ben Lieberman02:08:38

yeah sorry I phrased that poorly, I know I'm giving it a collection. thanks for pointing that out