Fork me on GitHub
Jim Newton10:02:56

I've been away from clojure for a while. Can someone remind my the clojure idiom for the following lispy idiom? I.e., the outer mapcat maps a binary function over two sequences qs and transitions, not concentrically but in parallel, and the inner map iterates a unary function over a list of pairs, while binding label and y for each pair.

(mapcat (fn [x pairs]
              (map (fn [[label y]]
                     [x label (qs y)])
            qs transitions)


Is qs a map?

Jim Newton11:02:13

qs in a vector, as I recall that is map-like


Right. I'm not really used to seeing them being used as a callable, heh.

Jim Newton11:02:20

how do you normally access vectors?


nth usually. But using vectors as callables should be perfectly fine if you know for sure that it's a vector and that the arguments are indices that exist within the vector.


You phrased your question as if there is an idiom for such a process. Are you sure there is one? I'm not exactly an expert here, but your code looks as concise as I would expect, given parts of clojure.core I remember.

Jim Newton11:02:49

I was under the impression that people avoid mapcat. and tend to use for comprensions. I'm happy with mapcat myself.


I use mapcat all the time. I tend to only really use for when I need nested loops.


mapcat is perfectly fine. concat isn't, in some conditions.

Jim Newton11:02:16

Question about group-by If I want to partition a given sequence into equivalence classes according to a probe function f, I can use (group-by f my-seq). E.g., (group-by first my-seq) will transform [[1 2 3] [10 20 30] [100 200 300] [1 5 6 7]] -> {1 -> [[1 2 3] [1 5 6 7]], 10 -> [[10 20 30]], 100 -> [[100 200 300]]} , In Scala I have a useful varient of this which does not collect the elements such as [1 2 3] , [10 20 30] , but rather applies a given function and collects those values. e.g., (group-map first my-seq rest) will give me a map from the first element of each sequence to list sequence of rests giving me the equivalent of {1 -> [[2 3] [5 6 7]], 10 -> [[20 30]], 100 -> [[200 300]]} Do we have such a cousin of group-by in scala? Of course I can create one in 2 lines, but the 2-line version creates lots of intermediate sequences.

(defn group-map [f seq g]  ;; untested
  (into {} (for [[k vs] (group-by f seq)]
             [k (map g vs)])))


You can write such a function in 3 lines without any intermediate sequences:

(defn group-map [f seq g]
  (reduce (fn [acc v]
            (update acc (f v) (fnil conj []) (g v)))
          {} seq))
And you can add transient and persistent! calls for that {} if needed.


I might be wrong but given my recent exposure to xforms it feels like that library might have something for that.


(require '[net.cgrand.xforms :as x])
(into {} (x/by-key first next (x/into [])) [[1 2 3] [4 5 6] [1 9 0]]) ;=> {1 [(2 3) (9 0)], 4 [(5 6)]}
something like that?


Ah, my intuition was correct. :D Nice.


Having these two ways of destructuring:

(fn [{:keys [name surname age]}]
  (str name ", " surname ", " age))

(fn [{:keys [:name :surname :age]}]
  (str name ", " surname ", " age))
I used to use the first syntax, just recently noticed the other. What is the difference? When each of these should be used? What is your personal/team preference and why?

💯 2
Alex Miller (Clojure team)14:02:46

You should use the first one

Alex Miller (Clojure team)14:02:51

Accepting keywords in the :keys was a workaround specifically to allow auto resolved keyword support, but that’s now better supported on :keys itself

Alex Miller (Clojure team)14:02:27

Ideally you are always specifying the local symbols that are being bound

Alex Miller (Clojure team)14:02:04

So they were added to support doing something like {:keys [::a]}

Alex Miller (Clojure team)14:02:08

Or {:keys [::an-alias/a]}

Alex Miller (Clojure team)14:02:40

But you can now better do {::keys [a]} or {::an-alias/keys [a]}

💯 1

ok, thanks a lot for clarification @U064X3EF3 🙇

Jim Newton20:02:17

I'm surprised to learn that (mapcat f set-of-sequences) returns a list rather than a set even in the case that f returns a set. I filled up memory with a huge list of repeated elements. E.g., (mapcat set #{[1 2 3] [2 3 4] [3 4 5]}) this returns (4 3 5 4 3 2 1 3 2) not #{1 2 3 4 5}


FWIW, the docstring is rather clear on that.

Jim Newton20:02:23

this is weirdly inkeeping with the documentation as (concat (set [1 2 3]) (set [2 3 4])) returns (1 2 3 2 3 4) not #{1 2 3 4}. that's shocking though

Jim Newton20:02:19

shouldn't the concatenation of two sets be the union? not the concatenation of the sets each converted to lists?


Depends on the abstraction. Union of the sets is, well, a union. Concatenation in Clojure operates at the seq level - from its POV, sets are just collections, it doesn't care about the intrinsics.


concat is also lazy. The notion of a set does not really allow for laziness


dedupe essentially builds a set, albeit not from the API POV. But it's lazy.


I was thinking based on contains? checks requiring realization. But i guess there could be partially realized sets. Sounds like a dangerous data structure that is not great for general purposes though


it would certainly violate an expectation that contains? is O(1)


there are surely some useful datastructures like this but calling them a generic set would be misleading


Hence my note about API. :) The phrase "notion of a set" is ambiguous, yeah.

Jim Newton20:02:44

good point. but still this was a surprising bug in my program. I thought it was an infinite loop. no it was just building a huge seqence of the same thing over and over. I was thinking the semantics were set-like, not sequence like. lesson learned!


yeah, the mental model I believe we are meant to have is "sequence operations take in 'seq-able' things and output a sequence."


for your case, using the transducer form and into might do exactly what you want


(into #{} (mapcat f) set-of-sequences)

👍 5
Alex Miller (Clojure team)20:02:27

Or use clojure.set/union

☝️ 1
Jim Newton11:02:43

Yes, once I understood the problem it is easy to fix by calling reduce using union. just surprising. I've been using Scala for a while, and the equivalent of mapcat in Scala preserves the monadic structure. At least from the point of view of the beginner.


Are reader tags tagging meta data or the form that the meta data is attached to? 1. |#a ^:a| a b 2. |#a ^:a a| b


I'm updating Calva's structural editing to be meta data aware,..


The form. The metadata by itself is not a valid form. And it's easy enough to test:

(defn test-reader [_ form]
  {:meta (meta form)
   :form form})

(set! *default-data-reader-fn* test-reader)

#a ^:x []
=> {:meta {:x true}, :form []}


Testing this:

^:x #a []
=> {:form [], :meta nil}
Not sure how to interpret the results.


The metadata was set on the results of the reader tag function. Try wrapping the whole statement in (meta ...).

🙏 1

I have successfully generated a Java class from clojure code using :gen-class I'm able to export it as a JAR, and use that class from another Java Project now I need to write clojure tests for that generated java class (not the clojure namespace that contains the code the generate the class) in my test namespace I naively (:imported my class (instead of the clj namespace), but its complaining that the class does not exists. It makes sense after all someone needs to compile that clj file into a class and add into the class path whats the best way of achieving this?


Never had to handle that, but I guess you could compile that namespace explicitly and then import the class (with the macro, not the :import keyword in the ns form). Of course, the output of compile will have to be on the classpath.