Fork me on GitHub
#clojure
<
2020-04-03
>
seancorfield00:04:33

It's also probably a good time to remind people to use threads when it looks like a conversation is going to take a deep dive and go back and forth a lot between just a few people...

thanks 4
👍 24
datran04:04:15

just tossing this out here: several months ago I watched a presentation where the presenter showcased chaining re-frame subscriptions to do some clever form validation. Does anybody know what talk I watched?

kwladyka08:04:49

TL; DR; I did https://github.com/kwladyka/form-validator-cljs to solve this - this is very small library bo do the job

otwieracz06:04:52

How does leiningen act when two of my project dependencies depends on the same library but in different versions?

dominicm07:04:48

It uses the maven algorithm, first version it finds wins

kelveden07:04:56

If you've not tried it already, lein deps :tree should help with working out what dependency is coming from where.

amalantony07:04:36

Hello everyone, how do I run an expression at the end of a ring handler once the response has been sent? The following is my use case:

(defn file-download-handler []
  ;; .... logic to generate a temp file
  (resp/file-response tmp-file-path))

; subsequent to sending the response, I want to delete the file at tmp-file-path
The file-download-handler sends a file response. Subsequent to sending this (temporary) file, I want to delete the temp file. This would be fairly straightforward if I could add an expression after (resp/file-response tmp-file-path) . But doing this means that the handler will not work as expected. How do I accomplish this?

phronmophobic07:04:00

I believe file-response just wraps the file in an inputstream. per the https://github.com/ring-clojure/ring/wiki/Concepts : > The contents of the stream is sent to the client. When the stream is exhausted, the stream is closed. you can make a proxy of an inputstream (using reify) that deletes the temporary file when closed.

phronmophobic08:04:24

you may also want to have some process that cleans up temporary files that are older than a certain threshold in case an exception in the handler causes the input stream to not clean up after itself for some reason

phronmophobic08:04:51

depending on how quickly the temporary files fill up disk space, another quick and dirty solution is just to have a process that deletes temp files older than X minutes

amalantony08:04:45

Thanks @U7RJTCH6J, I’ll probably go with the approach of periodically deleting the tmp files. Although I was primarily wondering if there was a way of knowing when a respone has finished in compojure. In Express/Node.js there is an event that is fired on response end, where you could handle cases like this.

phronmophobic17:04:57

there maybe, but I think it might depend on which server implementation you use (ie. http-kit, jetty, etc)

victorb09:04:44

not sure where to ask so I'll ask here. Anyone seen any implementations of QUIC made in clojure or a clojure wrapper for a QUIC java library?

adamfeldman22:04:47

(I went looking, just for fun. I haven’t used any of the tools below) https://github.com/quicwg/base-drafts/wiki/Implementations https://github.com/ztellman/aleph wraps Netty, but Netty only has unofficial, partial support for QUIC: https://github.com/protocol7/quincy

victorb08:04:44

Ah, those links are great. Thanks a lot! We're both in the same boat, just looking at QUIC for fun 🙂

🎉 4
g7s09:04:23

Is there any reason as to why calling (hash-map :a 1 ,,,) etc returns a clojure.lang.PersistentHashMap but calling (into (hash-map) {:a 1,,,}) will return a clojure.lang.PersistentArrayMap (up until the hash map threshold) ?

g7s09:04:30

No it doesn't actually (or I can't see it)

hindol09:04:51

For small maps, that's an optimization.

hindol09:04:13

It is documented somewhere.

hindol09:04:34

The answer should be in the source of hash-map and not in the source of into.

g7s09:04:43

I am using [org.clojure/clojure "1.10.1"]

g7s09:04:28

OK so I figured out that (type (hash-map)) will return clojure.lang.PersistentArrayMap what gives?

yuhan09:04:00

I asked this same question before, it seems like it's not really considered a problem

andy.fingerhut09:04:36

I think the attitude you will find among most Clojure programmers is: why do you care?

andy.fingerhut09:04:11

Both map types behave the same way, and as long as you do not have an array map with hundreds of elements, performance is good.

yuhan09:04:37

yeah but from a slightly OCD standpoint it just doesn't seem right

yuhan09:04:01

especially when there's explicit hash-map vs array-map public functions

andy.fingerhut09:04:34

It is perfectly right if you consider the concrete type to be an irrelevant detail, as long as behavior is correct, and performance is acceptable.

👍 4
yuhan09:04:52

Also Clojurescript behaves the "correct" way:

cljs.user=> (type (hash-map))
cljs.core/PersistentHashMap

gerritjvv09:04:18

clj Clojure 1.10.1 user=> (type (hash-map)) clojure.lang.PersistentArrayMap

yuhan09:04:51

But practically I think "hash map" is what the language refers to them as, so most people would reach for that function when building small maps, eg.

(apply hash-map [1 2 3 4])

g7s09:04:01

@andy.fingerhut I surely agree with the "why do you care?" thing, but I also agree with @qythium that because we have public hash-map and array-map functions one would expect to behave accordingly

andy.fingerhut09:04:17

Stop expecting that 🙂

yuhan09:04:31

So in that sense the language pushes the most common use-case to a more optimized performance

andy.fingerhut09:04:36

(is my recommendation, not a command -- care or expect all you want)

g7s09:04:57

@andy.fingerhut are you aware of the technical reason as to why (hash-map) has to return an array map?

g7s09:04:04

(if there is any ofc)

andy.fingerhut09:04:24

has to? I doubt there is a reason it does that by conscious design choice.

andy.fingerhut09:04:34

It has to because the code is currently written that way.

andy.fingerhut09:04:29

I can explain why the current code does, but that is a different question.

teodorlu09:04:05

In my understanding, an array map is more efficient when it has few elements, and Clojure tries to make it easy to do the efficient thing.

hindol09:04:10

Up to 9 elements, array map. 10 or more, hash map

andy.fingerhut09:04:59

O(1) for operations on an empty hash map or empty array map -- I just cannot bring myself to worry about the class.

g7s09:04:22

ok sure I can also understand why it returns an array map from the code I was asking if there is a more technical reason as to why it is designed this way

g7s09:04:35

apparently there isn't

teodorlu09:04:48

Isn't performace technical to you?

hindol09:04:57

I have a hunch there IS a reason.

gerritjvv09:04:14

user=> (type (hash-map)) clojure.lang.PersistentArrayMap user=> (type (hash-map :a 1)) clojure.lang.PersistentHashMap

andy.fingerhut09:04:17

Empty array map and empty hash map are both O(1) for all operations. So they are all fast.

hindol09:04:20

The same way quick sort resorts to insertion sort for small collections.

andy.fingerhut09:04:22

Performance is very important to me.

g7s09:04:37

@teodorlu then why (hash-map :a 1) returns a hash map?

gerritjvv09:04:46

static public IPersistentMap create(Map other){

gerritjvv09:04:50

is called with an empty map

gerritjvv09:04:57

returns the PersistentArrayMap

gerritjvv09:04:01

public static PersistentHashMap create(Object... init)

andy.fingerhut09:04:04

If (hash-map) returned a hash map, I doubt that someone filing a bug saying it ought to return an array map would be responded to. Nor vice versa.

g7s09:04:06

@gerritjvv yes I saw that

gerritjvv09:04:22

so, "yes" it is inconsistent.

gerritjvv09:04:34

I think the performance updates were introduced but also in an inconsistent way.

gerritjvv09:04:12

the main reason for the inconsistency may be that from a clojure perspective it doesn't matter.

👆 4
teodorlu09:04:26

@g7s > @teodorlu then why `(hash-map :a 1)` returns a hash map? Good point

teodorlu09:04:32

Hunch: we're digging into implementation details and we're not really supposed to care which IPersistentMap we're using. Observed inconsistencies may be accidental. I found (source hash-map) and (source map?) to be helpful.

gerritjvv10:04:13

^^ this is a much nicer answer than just "why do you care?" 🙂. and hints at the reasoning behind why the inconsistencies on types.

➕ 4
andy.fingerhut10:04:20

Sorry if my answer comes across as rude. Try to think of "why do you care?" not as "you should never care about such question", because I think you should ask such questions. The question "why do you care?" is meant from the perspective of "I think that, I you reflect on it as long as you care to, you will probably arrive to the conclusion that it isn't all that important of a distinction"

teodorlu10:04:32

I took @andy.fingerhut’s original comment as frank directness ("let's find the facts"), but I'm also inclined towards direct communication.

gerritjvv10:04:44

yup. @andy.fingerhut no worries, I dnd't perceive it as rude 🙂. but such answers (which sometimes I give myself) might be perceived differently especially by beginners to the community.

teodorlu10:04:46

@U05509S91 agreed. I'm often trying to balance "encouragement" and "fact-finding" myself. I sometimes find that getting totally obsessed with facts can inhibit any action, in a totally unproductive way. But this is hard stuff. I've found differentiating between observations and judgements to be helpful.

gerred12:04:00

I think I got mis-pinged here 😄

yuhan10:04:39

Yeah that was my conclusion as well - the hash-map function simply returns the literal {} which the clojure reader turns into an arraymap

g7s10:04:17

@teodorlu I completely agree with this reasoning but I also (for the sake of argument) believe that one might have somewhere in their code something like (into (hash-map) ,,,) and somewhere else could have dispatched a multimethod on the expected type (`clojure.lang.PersistentHashMap`) or could have an instance? check somewhere**. Reasoning about your program has a lot to do with consistent behavior and (hash-map) returning clojure.lang.PersistentArrayMap is not consistent. I am not arguing here that this is something that one should do, or is the clojure way of doing things. Let's also think about newcomers here.

yuhan10:04:15

Worth noting that I made this exact mistake before, not knowing that you could extend multimethods with interfaces (`IPersistentMap` in this case)

yuhan10:04:34

(defmulti mm class)

(defmethod mm clojure.lang.IPersistentMap
  [x] "matched")

(mm :not-a-map)
;; Unhandled java.lang.IllegalArgumentException
;; No method in multimethod 'mm' for dispatch value: class
;; clojure.lang.Keyword

(mm (array-map :a 1)) ;; => "matched"
(mm (hash-map :a 1)) ;; => "matched"

4
g7s10:04:52

Yes multimethods dispatch with isa? so you can built whole hierarchies (with derive etc) but I bet beginners will have a hard time debugging issues like that coming from inconsistencies

4
yuhan11:04:14

Running into this issue actually led me to learn about hierarchies and derive etc. In general while learning Clojure I found that bugs stemming from my misunderstanding would show themselves pretty obviously, and the process of solving them brings about a better understanding of the language and its idioms

yuhan11:04:55

(Not an argument for keeping inconsistencies in the language, of course)

teodorlu11:04:15

> Yes multimethods dispatch with `isa?` [...] I wasn't aware of that. Thanks! So for our case of hash maps, we could use the fact that

(isa? clojure.lang.PersistentHashMap clojure.lang.IPersistentMap)
... to do multimethod dispatch on the interface.

g7s11:04:48

@qythium I think sooner or later every clojure programmer will go through this process despite the fact that clojure is notorious of how easy it is to reason about, despite its dynamic nature

teodorlu11:04:13

And, of course, the reference covers this: https://clojure.org/reference/multimethods#_isa_based_dispatch I'll add my often last step to @qythium’s process: figuring out that the "weird" topic is actually explained well in the reference or a guide.

👍 4
teodorlu10:04:57

Yeah, it seems like it would be harder to define dispatch using predicate functions (`map?`) than dispatching directly on the type.

teodorlu10:04:01

By chance, do you like working with algebraic data types?

g7s10:04:37

Sure if I am working with types that is

teodorlu10:04:20

Because to me, I think I would reach for "I want to dispatch on some type" using Haskell, Elm or Rust, whereas in Clojure I'm more inclined to (a) just (cond ...) a bunch of predicates, and possibly (b) to use "less dispatch things", rather work with a more general map where I know what type is "under" each keyword.

g7s10:04:01

Well, I have a more open approach to things. I am sure that there is a time and place for everything. Multimethods and protocols are great tools for runtime polymorphism. Sometimes I use multimethods sometimes protocols and sometimes (cond ,,,) it depends on the context

Akash11:04:14

Hi, What’s an alternative for lein deps command with deps?

p-himik11:04:26

If you just want lein deps :tree, then it's clj -Sdeps.

Akash11:04:49

This is for my docker image. What I want to do it install all the dependencies and cache it. (Of course change it when the dependencies change).

p-himik11:04:41

Ah, I see. I just run something like clj -e '(println "Test run successful")' on a clean ~/.m2 folder and then cache the whole dir.

p-himik11:04:56

But now idea if tools.deps supports something like this.

Akash11:04:56

Okay nice. Let me try that. Thanks @U2FRKM4TW

Alex Miller (Clojure team)12:04:24

clj -Spath is sufficient

👍 4
Akash13:04:22

Even more simpler. Thanks @U064X3EF3

Adam Helins12:04:30

Regarding persistent queues, since the first part of a queue is implemented as a seq for easy popping, it means it would be trivial to prepend as well (which is what I need), wouldn't it? I cannot manage to find anything in the std lib or any method on the class to do so.

Jakob Durstberger12:04:28

I guess it wouldn’t be a queue anymore if you add to the front. Or did I misunderstand you?

Adam Helins13:04:39

Well, it would approach a double-ended queue I guess, unless I am missing something obvious

g7s13:04:47

The persistent queue is a FIFO queue so it makes no sense to prepend.

g7s13:04:03

Maybe you should look into finger trees

Adam Helins13:04:14

Thanks, that would indeed be a solution, but ideally I was hoping for something that would work out of the box with CLJS

g7s13:04:04

Sorry I have no idea if there is any lib in CLJS for that. I don't know if you can get away with JavaScript arrays..

dpsutton13:04:54

i think its a great idea to extend the persistent queue to be a deque

Adam Helins13:04:04

@U11BV7MTK That's why I am wondering if I am missing something obvious. The fact a persistent queue is implemented with a seq followed by a vector for easy popping from the start and appending to the end looks like it can just as easily be used for prepending and popping from the end.

dpsutton13:04:52

i agree with you. looking at it in java the fields are private so its a bit annoying (`f` and r are the seq and vector respectively). looking at cljs now

dpsutton13:04:58

but in cljs this makes it much easie:

dpsutton13:04:01

deque.impl> (.-front #queue [1 2 3])
(1)
deque.impl> (.-rear #queue [1 2 3])
[2 3]

Adam Helins13:04:04

But then, in CLJS, can you easily rebuild a queue by providing a seq and a vec?

dpsutton13:04:50

(defprotocol IDeque
    (-rcons [q e])
    (-lcons [q e])
    (-rpop [q])
    (-lpop [q])
    (-rpeek [q])
    (-lpeek [q]))

(extend-protocol IDeque
  PersistentQueue
  (-rcons [q e] (conj q e))
  (-lcons [q e] (PersistentQueue. (meta q)
                                  (inc (count q))
                                  (conj (.-front q) e)
                                  (.-rear q)
                                  nil)))

dpsutton13:04:53

deque.impl> (-rcons #queue [1 2 3] :new)
#queue [1 2 3 :new]
deque.impl> (-lcons #queue [1 2 3] :new)
#queue [:new 1 2 3]

Adam Helins13:04:05

Too bad it's not that simple in clj. I don't see any way we can overcome the fact that the seq and vec are private.

dpsutton13:04:11

private is just a suggestion. it gets verbose and reflecty maybe but it can be done

Adam Helins14:04:04

Yes but doesn't such dark magic incur a debt to performance ?

💯 4
dpsutton14:04:57

yeah definitely

Adam Helins14:04:37

Which is not what we want for a data structure. Should someone bother with opening a ticket about it? I mean, what are the chances of this grabbing some attention?

Ben Grabow16:04:30

As to the original question, turning the queue implementation into a dequeue implementation adds some pathological performance corner cases. Consider:

(-> (dequeue (range 100))
    (.lpop)
    (.rpop)
    (.lpop)
    (.rpop))
A naive implementation using the same strategy as a FIFO queue would cause an O(n) shuffle of data between the front and rear of the dequeue for each of the above operations.

dpsutton16:04:43

good point!

Ben Grabow16:04:47

I think this is one of the problems that the finger trees linked above are aimed at solving

Adam Helins11:04:55

Fair enough, yes. However, before diverting to deques, I was actually mostly interested by being able to prepend (a deque without rpop I guess). Then, I believe we wouldn't have that kind of problems (I might be mistaken).

Ben Grabow22:04:14

If your set of operations are: rpush, lpush, lpop (and not rpop), yes you could easily make your own using a list for the head and a vector for the tail, following the same implementation as PersistentQueue. It's a somewhat uncommon set of operations for a data structure, so that's a good enough reason for it to not be supported in the official clojure.lang bag of functions, but there's no reason you can't make and use it yourself!

kaosko14:04:15

anybody using jsonista? Is there a way to parse json only partially, similar to cheshire/parsed-seq? documentation looks a little thin

dmillett15:04:35

I have not used jsonista, but I have used regex to extract substructures from JSON text. Is that what you are looking for?

kaosko16:04:08

hmm you would first need to read the entire structure in memory for regex, no?

dmillett18:04:36

No. (re-find) will scan the text. If you have specific nodes you are looking for.

dmillett19:04:21

It depends what you want out of the underlying JSON. A transducer can be setup with a parser --> extractor for you data to handle large amounts.

kaosko18:04:32

ok thanks, I'll have to keep that in mind. for now, I found it simple do it with a java lib com.jayway.jsonpath.JsonPath/parse (I'm not a purist 🙂 ) but I'll have to benchmark memory consumption of it

Braden Shepherdson15:04:26

I'm struggling with a clean, idiomatic way to achieve this: I've got a customized search tree sort of structure, and I want to lazily return a sequence of leaf values. it feels like (apply concat (for [c children] (blah blah recursion))) is not the Right Way to achieve this.

Braden Shepherdson15:04:08

then again, maybe it is - it's lazy and it doesn't blow the stack or anything.

yuhan15:04:50

lazy-cat sounds like what you're looking for - apply wil realize the for expression

noisesmith15:04:12

apply doesn't realize lazy values

noisesmith15:04:21

the function might do it, but apply itself never does

noisesmith15:04:51

user=> (apply (fn [x & _] x) (range))
0
this would not have returned if apply was eager

yuhan15:04:19

sorry! I had a misunderstanding

dpsutton15:04:30

(remove branch? (tree-seq branch? children root)) might work

➕ 8
Braden Shepherdson15:04:09

wow, tree-seq looks great.

lilactown15:04:51

it’s one of the things that makes it feel impossible to use another language 😛

lilactown16:04:03

“There’s a (clojure.core) function for that”

Braden Shepherdson16:04:49

"There's a (clojure.core) function for this page of complicated Java."

💯 4
amalantony17:04:13

Why do a lot of clojure/core.clj functions have redundant arity definitions? For instance:

(defn vector
  "Creates a new vector containing the args."
  {:added "1.0"
   :static true}
  ([] [])
  ([a] [a])
  ([a b] [a b])
  ([a b c] [a b c])
  ([a b c d] [a b c d])
  ([a b c d e] [a b c d e])
  ([a b c d e f] [a b c d e f])
  ([a b c d e f & args]
     (. clojure.lang.LazilyPersistentVector (create (cons a (cons b (cons c (cons d (cons e (cons f args))))))))))
There are 8 arities and several of them are redundant. Is this purely for performance reasons or is there something more to it?

souenzzo17:04:29

There is a small part of this talk that describe this problem And show that if clojure compiler where build in clojure, it could be solved way more elegantly https://www.youtube.com/watch?v=eDad1pvwX34

👍 4
Adam Helins19:04:50

Is there a particular reason why the different types of sequences do not implement IPersistentStack? To me at least, this is not intuitive as sequences are "logical lists". Is it because a seq might then pop from a wrapped data structure which is not optimized to pop efficiently like a list? For instance :

(pop (list 1))       ; works as intented
(pop (cons 1 '()))   ; throws
(pop (seq [1]))      ; throws

potetm01:04:36

cons, seq, map, and filter are sequence abstractions. They’re designed to be lazy. They can be backed by any number of concrete data types or, potentially, none at all (e.g. infinite sequences). list, vector, and PersistentQueue are all concrete data types. They are designed to hold all contents in memory and to be very efficient at various operations, depending on the type. For example, list has a very efficient prepend operation, but poor indexed access. vector has a very efficient append operation and very efficient indexed access. In clojure, peek, pop. and conj are queue functions. They’re only available on concrete data types which implement the IPersistentStack interface. (That includes lists, vectors, and PersistentQueue.) This restriction is to quasi-guarantee that peek and pop will be extremely efficient (usually requiring no more than a single allocation). This also guarantees that each function keeps the same data type as the original collection (unlike sequence functions).

Adam Helins08:04:17

Yes, I understand that all of this is organized for the sake of efficiency, yet I sometimes find it a bit odd. For instance, regarding how conj fits in all that :

(class (conj (range 1))) ; Cons, you can conj to a seq
(pop (range 1))          ; Throws

Adam Helins08:04:00

I have been using Clojure for years but once in a while I reconsider something that basic

potetm13:04:54

I honestly didn’t know conj worked on sequences. I agree that’s odd. Though, I would never use it for them intentionally. I would never even use cons for sequences intentionally. Ordinarily, you shouldn’t be dealing with individual items or indices when operating on sequences. You should always consider them as a sequence. conj is kind of an oddball operation. It’s the default way to add to a collection (as opposed to cons in other lisps). For stacks, it means “push.” For sets, it means “add.” For lists, it means “append.” For maps, it means “add this key-value pair.”

potetm13:04:26

My rule of thumb that allows me to not think about it: map, filter, concat are for sequences. conj, peek, pop, assoc are for when you want to control the concrete data type.

myguidingstar19:04:22

@contact904 there's https://walkable.gitlab.io Clojure is data-oriented, so the preferred way is through data, not objects, therefore no ORMs

✔️ 8
Eduardo Mata19:04:23

I have two vectors of maps such as Vector #1 [{:_id 'abc} {:id 'bcd} ... n]_ and vector #2 ['zxy 'xyz 'abc ... n] . My goal is to filter out maps that ids' does not match the ids of vector #2. Are the functions filter and .contains a good approach? Or should I do a reducer?

myguidingstar19:04:15

@contact904 Yes, that's enough. Or you can convert vector 2 to set and use contains?

Eduardo Mata20:04:54

okay so I now have vector #1 filtered. I have another vector #3 [{:id 'abc :someList [a b c d] ... n] What I did is

(defn some-reducer
   [vectorOne {:keys [id someList] :as vectorThree}]
   (cond (some? vectorThree)
         (map #(cond (= (:id %) id)
                     (assoc % :someList someList)
                     :else %) vectorOne)
         :else vectorOne))
(reduce some-reduce vectorOne vectorThree)
Is this a good way to do so? It works but I wonder if there is a more clojure idiomatic way to do it. Edit: My goal is to assoc a keyword with the value to be someList when the ids are successfully matched.

souenzzo21:04:58

My solution

(letfn [(index-by [f coll]
          (into {}
                (map (juxt f identity))
                coll))]
  (let [els [{:id 1} {:id 2}]
        els-by-id (index-by :id els)]
    (assoc-in els-by-id [1 :v] 42)))
      
=> {1 {:id 1, :v 42}, 2 {:id 2}}

Graham Seyffert21:04:01

> My goal is to assoc a keyword with the value to be `someList` when the ids are successfully matched

Graham Seyffert21:04:24

So you want your output to look like {'abc [a b c d]}?

Graham Seyffert21:04:39

Oh I think I see what you want

Graham Seyffert21:04:15

Like this? -

(defn my-transform
  [v1 v2 some-list]
  (let [ids (set v2)]
    (into []
          (comp (filter (comp ids :id))
                (map #(assoc % :someList some-list)))
          v1)))

=> #'user/my-transform
(my-transform [{:id "a"} {:id "b"} {:id "c"}]
              ["a" "c"]
              ["a" "b" "c" "d"])
=> [{:id "a", :someList ["a" "b" "c" "d"]} {:id "c", :someList ["a" "b" "c" "d"]}]