Fork me on GitHub
#clojure
<
2022-02-26
>
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)])
                   pairs))
            qs transitions)

p-himik11:02:03

Is qs a map?

Jim Newton11:02:13

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

p-himik11:02:38

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?

p-himik11:02:52

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.

p-himik11:02:45

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.

1
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.

Ed11:02:50

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

p-himik11:02:44

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)])))

p-himik11:02:04

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.

1
p-himik11:02:02

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

Ed11:02:12

(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?

p-himik12:02:24

Ah, my intuition was correct. :D Nice.

greg14:02:17

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
greg15:02:26

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}

p-himik20:02:59

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?

p-himik20:02:16

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.

dpsutton20:02:26

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

p-himik20:02:31

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

dpsutton20:02:40

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

dpsutton20:02:08

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

dpsutton20:02:37

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

p-himik20:02:06

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!

lilactown20:02:46

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

lilactown20:02:25

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

lilactown20:02:20

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

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

Or use clojure.set/union

☝️ 1
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.

pez22:02:35

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

pez22:02:23

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

p-himik22:02:39

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 []}

pez23:02:31

Testing this:

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

p-himik23:02:30

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

🙏 1
plins22:02:08

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?

p-himik22:02:25

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.