Fork me on GitHub
#beginners
<
2024-07-01
>
Jim Newton09:07:28

Is there a standard (or recommended) way to extract documentation from a clojure project based on docstrings? If I write good docstrings, should I be able to extract some stand-alone documentation? Or do I need to write the documentation by hand?

Ben Sless09:07:03

Mind you, this relies on static analysis, but it works really well https://github.com/borkdude/quickdoc

shivang raina09:07:28

How do create a pipline for data transformation using -> or ->> , If some of the fns in the pipeline has different order in which the next transformation has to be passed ? for example:

(defn- transform
  [ops new-value]
  (->> ops
       (map-indexed hash-map)
       (into {})
       (assoc % :values new-value)))
Here ->> will not work for assoc. How to handle these cases cleanly?

daveliepmann09:07:42

Because the issue is caused by an into at the end, I might prefer somethng like

(defn- transform [ops new-value]
  (assoc (->> ops
              (map-indexed hash-map)
              (into {}))
         :values new-value))
I have less desire for threading than many, so that would lead me to
(defn- transform [ops new-value]
  (assoc (into {} (map-indexed hash-map ops))
         :values new-value))
and then I'd consider whether I should be using a transducer instead of seq API:
(defn- transform [ops new-value]
  (assoc (into {} (map-indexed hash-map) ops)
         :values new-value))
and to me that feels nice and expressive. YMMV

tomd09:07:16

You could use as-> , but not everyone is a fan:

(defn- transform
  [ops new-value]
  (as-> ops $
    (map-indexed hash-map $)
    (into {} $)
    (assoc $ :values new-value)))

phill09:07:23

You can use as-> to thread the value of each form into any position you want in the next form. Or you can use -> at the outermost level and nest ->> (or as->) within it for the forms that need to receive the threaded input at the end or at an arbitrary point.

🤝 1
shivang raina09:07:31

Thanks Guys! :saluting_face:

shivang raina09:07:25

@U05092LD5 This might be a separate discussion, but how does the transducer solution work? (I don’t have experience with transducers).

shivang raina09:07:38

@UE1N3HAJH Also there a reason why people do not prefer as-> ?

tomd09:07:07

I think a lot of it comes from this: https://stuartsierra.com/2018/07/15/clojure-donts-thread-as

👀 1
👍 1
tomd09:07:08

I'm not sure I agree, but vocal people around here do, so if that matters, or you agree with Sierra's arguments, then you may prefer one of the other solutions here

tomd09:07:32

Possibly something Sierra would prefer:

(defn- transform
  [ops new-value]
  (-> ops
      (as-> $ (map-indexed hash-map $))
      (as-> $ (into {} $))
      (assoc :values new-value)))
(probably with meaningful names rather than $s)

👍 1
Ed09:07:24

I think for something like that I'd probably use a let. Maybe something like

(defn- transform [ops new-value]
    (let [result (into {} (map-indexed array-map) ops)]
      (assoc result :values new-value)))  
But you may want to think about splitting it into separate functions? Often I find that mixing sequence functions with maps is mixing layers and in the long term it makes sense to separate them.

tomd09:07:35

My assumption when answering these questions is that examples are just examples (e.g. this could be a much longer pipeline), but yes I agree threading isn't really necessary in the example because it is short.

👍 1
daveliepmann09:07:58

> how does the transducer solution work? (I don’t have experience with transducers). Sure, so, one of Clojure's major innovations when it was created was the https://clojure.org/reference/sequences. This abstraction was a major step forward from its predecessors (Java and Common Lisp) and made everyday work simple and consistent: map, filter, it all operated on sequential data the same way, with laziness. Transducers came later, and solved a largely-overlapping set of problems, but with different trade-offs. Most importantly, they are eager not lazy, don't wastefully produce a seq at each intermediate step, and can be used for both collections (like maps or sets) and sequences (like lists and vectors). (See this https://clojure.org/guides/faq#transducers_vs_seqs for more.) Most common usage uses into the way I showed, and is often effectively the same as ->> or ->. I believe Rich has stated publicly that if he were writing Clojure from scratch today, transducers would be more central than the seq API. It's important to see that (->> ops (map-indexed hash-map) (into {})) is just a macro-rearrangement of (into {} (map-indexed hash-map ops)) (calling into on the result of a sequence operation) whereas (into {} (map-indexed hash-map) ops) is a transducer operation (not seq) because ops is outside map-indexed, invoking its 1-arity (i.e. "Returns a stateful transducer when no collection is provided." from its docstring).

👀 1
shivang raina10:07:28

Thanks @U05092LD5. Really appreciate it.

👍 1
Ed10:07:00

Some other things to note about transducers are • where you would use ->> to combine seq operations together, you now use compsequence can be used to lazy-ness so these two things are similar

(->> (range 10)
       (map inc)
       (map #(* % %))) ;; => (1 4 9 16 25 36 49 64 81 100)

  (sequence (comp (map inc)
                  (map #(* % %)))
            (range 10)) ;; => (1 4 9 16 25 36 49 64 81 100)
but once you have combined some operations together with comp you have a thing that you can pass around and combine with other transducers, which might be useful in decoupling your code, but sometimes might just make it more complicated than it needs to be 😉

👀 1