Fork me on GitHub
#clojure
<
2017-09-13
>
bfabry00:09:37

yes... a record already implements java.util.Map

Alex Miller (Clojure team)00:09:58

if you want to do something custom, that’s what deftype is for

bfabry00:09:33

also potemkin's def-map-type and def-derived-map may be of use to you if you're doing that https://github.com/ztellman/potemkin

qqq00:09:26

(doseq [i (range 100000)] (curse!))
I just spent an entire afternoon debugging the following problem (I was half convinced it was a jvm / hardware issue)
(defn foo [x] ...) // foo takes only a arg
(defn bar [ ... foo  ...] ... (foo 20)) // foo here shadows global foo
(bar ... foo=hash-map)

so now, I don't get an error because hash-maps can serve as functions

and for the life of me, I couldn't figure out why function foo wasn't being called
question: is there some linting tool / boot / clojure option that will loudly scream at me if a local binding shadows something else ?

bfabry00:09:15

but what about the times when shadowing is what you want? neither eastwood or cursive warn about shadowing. maybe check on kibit and joker i guess but I doubt they do either

cfleming00:09:02

Warning on shadowing is definitely something I’ve considered, but it is what you want freqently.

cfleming00:09:16

(i.e. a local called map or list or something)

cfleming00:09:53

It would have to be a fairly unobtrusive warning, but it’s bitten me from time to time too.

bfabry00:09:45

cursive may have caught this if you didn't use foo anywhere else, ie the (defn foo would be grayed out because not used anywhere

qqq00:09:09

it's used elsewhere

qqq00:09:25

what threw me off was the combination of: foo only took one argument, hashmaps can be treated as functions of 1 arg

qqq00:09:42

so instead of getting a runtime error / exception at where (foo x) happens, I just silently get the wrong answer

tanzoniteblack00:09:15

@qqq what do you use as an editor?

qqq00:09:28

I use emacs / cider

noisesmith00:09:39

eastwood will warn about shadowing iirc

tanzoniteblack00:09:51

doesn't exactly fix your issues, but I use https://github.com/Fanael/rainbow-identifiers to make it so these kind of things jump out at me more easily

bfabry00:09:18

ah you're right it does

bfabry00:09:58

limiting it to "and is called as a function" is smart, and would probably drop the number of false positives a lot

tanzoniteblack00:09:40

heh, what do you know

(defn foo [x]
  x)

(defn bar []
  (let [foo {:cat "cat"}]
    (foo :cat)))
https://github.com/clojure-emacs/squiggly-clojure does have eastwood reporting a warning at the (foo :cat) line

tanzoniteblack00:09:02

local: foo invoked as function shadows var: #'yummly.metadata.ads/foo

qqq00:09:19

@bfabry @tanzoniteblack: thanks for the suggestions

pbaille08:09:09

Hi, i've got a plumatic.plumbing question: is there any namespace qualified keywords support for fnk and defnk?

pwrflx12:09:32

what's the standard way of organizing tests to various files? eg if I have a file component.clj, should I place by deftests into that? or create a separate component_test.clj? If yes, where should that file go to? into the same dir as the component.clj or to a parallel directory structure?

jumar12:09:35

@pwrflx most of the time I use separate file with "_test" suffix located in "test" directory under project root. "test" directory pretty much mirror the "src" dir.

jstew12:09:40

@pwrflx: If you're using leiningen, running lein new <proj> will give you some hints about that

jumar13:09:11

that's good pint too - you should end up with hierarchy that I've just described.

pwrflx13:09:37

@jumar @jstew will look at it thx

the2bears16:09:15

Looking for some inspiration here. I've inherited a stack overflow that happened at least twice, but it's not a regular occurrence and it hasn't happened since before I arrived.

the2bears16:09:28

java.lang.StackOverflowError at clojure.lang.RT.seq(RT.java:507) at clojure.core$seq__4128.invoke(core.clj:137) at clojure.core$filter$fn__4580.invoke(core.clj:2679) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:49) at clojure.lang.RT.seq(RT.java:507) at clojure.core$seq__4128.invoke(core.clj:137) at clojure.core$filter$fn__4580.invoke(core.clj:2679) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:49) at clojure.lang.RT.seq(RT.java:507) at clojure.core$seq__4128.invoke(core.clj:137) at clojure.core$filter$fn__4580.invoke(core.clj:2679) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:49) at clojure.lang.RT.seq(RT.java:507) at clojure.core$seq__4128.invoke(core.clj:137) at clojure.core$filter$fn__4580.invoke(core.clj:2679) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:49) at clojure.lang.RT.seq(RT.java:507) at clojure.core$seq__4128.invoke(core.clj:137) at clojure.core$filter$fn__4580.invoke(core.clj:2679) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:49) at clojure.lang.RT.seq(RT.java:507) at clojure.core$seq__4128.invoke(core.clj:137) at clojure.core$filter$fn__4580.invoke(core.clj:2679) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:49) at clojure.lang.RT.seq(RT.java:507) at clojure.core$seq__4128.invoke(core.clj:137) at 

the2bears16:09:51

It just repeats the same trace until it overflows.

the2bears16:09:21

I'm trying to interpret this, obviously lazy seqs and a filter(?) There are only a handful of places in the code using 'filter', and a few more using 'remove', but nothing yet that seems out of the ordinary and would cause this.

the2bears16:09:48

Unfortunately I cannot reproduce this, any hints at what I might also look at?

tbaldridge16:09:36

remove works via filter, so that's one thing to keep in mind

tbaldridge16:09:55

if you look at the entire stack trace you should see the point where it enters the loop, and that's the best place to start

the2bears16:09:08

Yeah, I figured and confirmed that in the 'remove' source, so I've been tracking that as well.

the2bears16:09:42

Yes to the entire trace, I fear it was lost and this is the start of what was saved 😛

hiredman16:09:29

are you sure you aren't just applying filter to a seq a few million times?

hiredman16:09:03

(doall (nth (iterate (partial filter number?) s) 100000)) will result in a similar looking stacktrace

hiredman16:09:20

(where s is some seq)

the2bears16:09:23

@hiredman that's certainly possible, I'll re-examine with this in mind. Unfortunately nothing jumped out before - looks like single applications of the filter to the collections in question.

the2bears16:09:33

Thanks for the help @tbaldridge and @hiredman

hiredman16:09:09

concat doesn't show up in your stacktrace, but it is a common culprit in stack overflows when used repeatedly

ghadi16:09:08

@the2bears do you have any non-clojure* stacktraces?

ghadi16:09:40

it's possible that some lazy sequence is not using the lazy-seq macro correctly...

the2bears16:09:08

I looked at concat as well, it's used in the code once or twice but I figured since it's not in the trace it's not the issue.

hiredman16:09:28

you don't have the whole trace

the2bears18:09:25

@hiredman, but how to get the 'whole trace'. I'm using your example code that generates a similar trace but any trace I get:

the2bears18:09:33

Caused by: java.lang.StackOverflowError
	at clojure.core$seq__4357.invokeStatic(core.clj:137)
	at clojure.core$filter$fn__4812.invoke(core.clj:2700)
	at clojure.lang.LazySeq.sval(LazySeq.java:40)

the2bears18:09:49

This is from -main to a function that calls your example

hiredman18:09:12

something thing is filtering your stacktraces then

the2bears18:09:29

So I'm not seeing where this actually started. A thing I'd like if I hope to capture this if it happens again.

the2bears18:09:36

Maybe, CIDER?

the2bears18:09:52

Cool, thanks!

hiredman18:09:02

sure, maybe, I dunno, people keep writing libraries that do it for some stupid reason

the2bears18:09:27

thanks for the suggestion

hiredman18:09:38

from cider you should be able to get the whole trace if you (.printStackTrace *e)

the2bears16:09:32

@ghadi, unfortunately this sample is what I have (... and lots of it repeating).

the2bears16:09:58

Agreed, I'll have to wait to see if this happens again and make sure the whole trace is grabbed at the time.

the2bears16:09:33

I think once I have that the investigation will go smoother 🙂

ghadi16:09:51

Throwable->map in Clojure 1.8 is really useful

the2bears16:09:59

Not familiar with that, I'll look into it, thanks!

aengelberg17:09:09

can anyone help me understand this behavior with lazy-seq (Clojure 1.8)?

user> (def s (lazy-seq
               (println "resolving seq")
               (cons 1 (lazy-seq
                         (println "resolving seq2")
                         (cons 2
                               ((fn [x]
                                  (lazy-seq
                                    (println "boom" x)
                                    (/ 1 0))) 
                                :x))))))
#'user/s
user> (first s)
resolving seq
1
user> (second s)
resolving seq2
2
user> (nth s 2)
boom :x
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)
user> (nth s 2)
boom nil
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)

aengelberg17:09:51

One of the later lazy seqs throws an error, but when I try to realize that seq again, the body somehow loses the values in its lexical closure

dealy17:09:56

More pubsub questions. Can a single input-channel handle having more than one publication with different topic-fn? My reading of the docs leads me to believe this is true. However as soon as I create a second pub on the input-ch (with different topic-fn) the notifications stop working on the first pub. Any thoughts?

ghadi17:09:19

the "thunk" inside a lazy seq is marked with ^:once metadata, making it a "once-fn" -- practically this means that the closed-over values disappear and are cleared after the only legit invocation of the function @aengelberg

aengelberg17:09:29

but then why is it trying to call the same function again if it errors, if it's designed to only be called once?

hiredman17:09:41

because of the error

hiredman17:09:56

lazy-seqs cache the result value, but don't cache the error

Alex Miller (Clojure team)17:09:09

there’s a ticket about this I think

Alex Miller (Clojure team)17:09:35

but really the general policy is that we don’t make any guarantees about seqs that blow up

aengelberg17:09:52

I guess I'll make sure my seq doesn't blow up then

Alex Miller (Clojure team)17:09:24

that’s not the particular ticket I was thinking of (although it’s the same thing), was thinking of: https://dev.clojure.org/jira/browse/CLJ-2069

ghadi17:09:48

i wonder if there is a perf impact to that ^

Alex Miller (Clojure team)17:09:57

this is a good example of a ticket that I have a hard time predicting how Rich will react to it - could as easily be declined as accepted

Alex Miller (Clojure team)17:09:04

based on things I’ve heard him say in the past

aengelberg17:09:02

The reason I encountered this is I had to turn a lazy seq into an iterator that was then passed to a Java class which I guess decided to call .next despite getting an error...?

aengelberg17:09:14

Oh wait, it looks like the standard call pattern .hasNext followed by .next breaks down when there's an error in the underlying seq.

user> (def i (.iterator ...)) ; same seq as above
#'user/i
user> (.hasNext i) (.next i)
resolving seq
true
1
user> (.hasNext i) (.next i)
resolving seq2
true
2
user> (.hasNext i) (.next i)
boom :x
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)
boom nil
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)

aengelberg17:09:43

Seems like this is a standard use case that shouldn't break?

aengelberg17:09:58

I'll vote for the jira ticket 🙂

Alex Miller (Clojure team)17:09:24

some workarounds in your current situation - convert your lazy seq usage to an eduction with transducer (if that’s possible)

Alex Miller (Clojure team)17:09:24

or call (clojure.lang.SeqIterator. the-seq)

Alex Miller (Clojure team)17:09:37

which is kind of some now unused inside baseball

aengelberg17:09:19

is SeqIterator a different impl than the standard .iterator call?

Alex Miller (Clojure team)17:09:43

it is, although it’s hard for me to say if either of these will actually get past your issue without trying them

aengelberg17:09:47

I'll try it out, thanks

aengelberg17:09:34

Is the ^:once metadata on the generated fn necessary to prevent some sort of memory leak? Or is it just for a perf improvement? I'm curious if I can get around this by manually calling (clojure.lang.LazySeq. (fn [] ...)) instead of (lazy-seq ...)

ghadi17:09:44

IIRC, the thunk function will be set to null and collected after LazySeq has invoked it. The ^:once metadata allows the closed-over values to be collected after they are used and before the thunk function has even finished executing.

aengelberg17:09:25

I don't think the thunk function is set to null, since as you said, it doesn't cache errors, and I'm getting errors again when I call the seq multiple times

aengelberg17:09:32

so it must have held onto the thunk

ghadi17:09:36

it's not set to null if it throws

aengelberg17:09:38

I assume that if I don't keep any references to the head of the lazy seq itself, I shouldn't need to care about how LazySeq manages its pointers internally?

aengelberg17:09:32

Thanks for the help, Ghadi and Alex

aengelberg17:09:52

and @hiredman

johanatan17:09:18

hi, does anyone know if it is possible to cancel a promesa promise on the JVM or otherwise workaround the absence of p/timeout on the JVM?

tanzoniteblack18:09:15

@U0E98NQG2 I know that timeout doesn't exist in clj version, but doesn't cancel still exist? https://github.com/funcool/promesa/blob/master/src/promesa/core.cljc#L225

johanatan18:09:49

Ah, I think it does. Good catch

johanatan18:09:24

In which case, it is trivial to implement a p/timeout which makes one wonder why it isn't included. 🙂

tanzoniteblack18:09:49

No, there are no alternative option, it is just not implemented. PR is welcome for that part :D

hiredman19:09:17

what client are you using?

dadair19:09:26

just raw Java Interop

tbaldridge19:09:53

I'd suggest moving this into a thread, since you're performing a blocking call on the KafkaConsumer

tbaldridge19:09:21

But this is unrelated to your GC questions

dadair19:09:13

Would that just be a (thread ..) instead of (go ..)?

tbaldridge19:09:01

yes, but also switch your put to a a/>!! to properly handle backpressure

tbaldridge19:09:15

as it is now, if producers create data too fast, you're going to hit an exception on that put!

dadair19:09:09

that makes sense, thanks! I'm reading off the channel as follows:

(a/go
    (loop []
      (when-let [req (a/<! actions-in)]
        (let [{:keys [errors actions statements]} (dispatch req sessions)
              rid (:requestId req)]
          (if (seq errors)
            (a/put! actions-out {..})
            (do
              (when (seq actions)
                (a/put! actions-out {..}))
              (when (seq statements)
                (a/put! statements-out {..})))))
        (recur)))
...
Could that be improved as well?

tbaldridge19:09:49

well this is a bit different. In this case your read is using a/<! so that's fine

tbaldridge19:09:16

be very, very careful with a/put! a good rule of thumb: Never use a/put! without also giving it a callback that handles the backpressure

tbaldridge19:09:09

a/put! doesn't wait for a slot to be available in the channel, so it basically creates a unbounded queue, and there's stuff in core.async to throw exceptions when such behavior is detected .

dadair19:09:55

awesome, thanks for the advice!

dadair19:09:20

(a/go
      (loop []
        (when-not @poison
          (let [records (.poll ^KafkaConsumer consumer (:poll-timeout opts))]
            (doseq [r records]
              (a/put! ch (deserialize-fn (.value ^ConsumerRecord r)))))
          (recur)))
...

hiredman19:09:33

actually, I'd say that heap graph doesn't actually show anything

hiredman19:09:42

periodic garbage collection and noise

laujensen19:09:45

Do Ring sessions guarantee any kind of uniqueness?

tbaldridge19:09:01

(it shows a properly working GC, imo)

dadair19:09:37

just wasn't sure if there was something I could do on my end to make the consumer not create so much garbage to collect 🙂

hiredman19:09:53

I wouldn't worry about it

tbaldridge19:09:45

It does seem odd to me though that there appears to be about 400MB of garbage created every minute. Most likely due to something else in the system, but that's rather high for a mostly idle app.

Zor19:09:42

ha, garbage generation 🙂 something that's tricky to observe and get insights about

Zor19:09:00

400MB of garbage says little about whether it's 400 x 1MB or 40K x 100KB (out of hat values. workload specific)

tbaldridge19:09:22

@dadair classic use case for a memory profiler.

tbaldridge19:09:43

In Yourkit you can capture all allocations over a few minutes and then the profiler will tell you where the allocations came from.

Zor19:09:55

fwiw, I don't spot any huge garbage generating Clojure construct in the code dadair pasted

tbaldridge19:09:37

Agreed, it's probably in some sort of logging library.

Zor19:09:14

and ehm - sorry for sidetracking - is the topic "garbage generating Clojure constructs" interesting to some ? I have dusty personal code in a drawer that was written with the aim of exploring this topic.

tbaldridge19:09:55

Sure, but it has to be done carefully. Stuff like allocation removal in the JVM makes this a tricky subject.

Zor19:09:35

Haha. I used btrace ! I have been way too shy to endure peer review, but looking back, what's the point of keeping it private

Zor19:09:21

I have never managed to get complete certainties about some things. It makes the README a bit hard to write, since the results are very much open to interpretation

dadair19:09:25

Trying to run VisualVM memory profiling but it causes the app to hang and eventually errors out saying it can't establish a profiling connection -- sampling works, just not profiling (but I'll move this discussion to #beginners)

Zor19:09:38

try yourkit first imo, VisualVM is 1) more trouble 2) less actionable results (or maybe its just harder for me)

dadair19:09:32

YourKit/JProfiler have a pricing model that precludes me from using them 😞

noisesmith19:09:29

yourkit is free if you make a good usable open source clojure library - which might be within your reach

Zor19:09:56

there is a personal license available for yourkit that is not advertised on their website. $99/year. that's hardly starving hacker rate, but it might be within your range compared to their pro-oriented rates

Zor19:09:26

use the 30-day free trial period to meditate whether or not that price is worth it to you. it just might.

Zor19:09:28

you may also soon realize your memory issues are not blocking you from doing whatever it is your doing. the free trial period will also cover that, for free 🙂 (premature optimization yada, yada)

dadair19:09:37

agreed, the memory issues aren't blocking. I'm just trying to reduce the bloat of our dev stack as 4 JVMs on a Macbook Pro + IntelliJ + etc can get a bit bloated

Zor19:09:33

dadair : w.r.t. reducing dev bloat, you should not be worried about garbage generation

Zor19:09:25

its mostly going to impact GC metrics (how often, how long) which are hardly noticeable unless you're doing 60Hz stuff

Zor19:09:15

set smaller java max heap sizes, let the JVM explode whenever it grows outside of what you're comfy giving it

Zor19:09:20

I wouldn't tune my stack to my development environment too hard - it's already enough work to tune it for production environments

mbjarland20:09:37

I have two maps where some of the map values are byte arrays.How would I go about comparing the maps for equality as (= (byte-array [1]) (byte-array [1])) returns false? i.e. is there something I could use in clojure or do I need to write something that walks the maps and runs Arrays/equals or equivalent manually?

noisesmith20:09:59

@mbjarland one option would be replacing each byte-array with a (vector-of :byte)

noisesmith20:09:39

which could be done with a map / into or a tree-walk, depending on the complexity of your data structure

mbjarland20:09:57

@noisesmith…looks promising. Thanks for the pointer

fabrao20:09:24

Hello all, is there an easy way to convert some key value of a map instead coping all key-values one by one? [{:a 1234 :b "some" ...} {:a 1223 :b "some" ...}] to {[:a "new_one" :b "some" ...} [:a "new_2" :b "some" ...}] in this case I´ll convert :a value to new :a value

noisesmith20:09:56

@fabrao (map #(update % :a f) ...) ? I don’t know how you come up with your new values…

noisesmith20:09:54

but if you have a function f that takes 1234 and returns “new_one” and takes 1223 and returns “new_2" that will work

fabrao20:09:08

well, I´ll get the :a value transforme it and update it into array of map

fabrao20:09:29

humm, I´ll try it now

fabrao21:09:21

@noisesmith that was too easy, thank you, it worked. I see I´m clojure newbie

noisesmith21:09:50

@fabrao you’ll find that most things that involve data transforms are quite simple in clojure, though sometimes you’ll need to reconsider specific approaches (eg. using associative data structures instead of linear scans, updating a cache of derived data instead of doing in place updates by index, using keyed accumulators in reduce instead of mutating an item as you iterate a collection…)

fabrao21:09:26

@noisesmith so updating map is what kind of approaches?

noisesmith21:09:07

it’s one of the simple things I didn’t list

noisesmith21:09:22

I only gave examples of the ones that tend to be a little trickier for people

fabrao21:09:12

ok, sometimes the imperative programming is in my memories 🙂

noisesmith21:09:40

sometimes what helps is thinking of how you would do things in SQL

noisesmith21:09:48

we have group-by etc.

noisesmith21:09:32

(update m :a inc) is a lot like update table some_row set a = (a + 1)

hmaurer22:09:46

That confused me more than it helped to be honest 😄

noisesmith21:09:14

ugh - that’s not good SQL but hopefully you get the idea

hmaurer22:09:26

@fabrao think of it as function composition. Clojure has two basic functions to manipulate maps, assoc and update. You would use the former when you want to set the key of a map to a value. It takes a map, a key and a value, and returns a new map with your given value under your given key

hmaurer22:09:17

If a value is already present in the map for the key you want to assoc, it will override it, e.g.

hmaurer22:09:29

Now, if you wanted to transform the value under :a in the map by some function f (for example, increment it with inc), you could do this:

hmaurer22:09:46

But that’s pretty verbose. Clojure core includes the function update, which takes a map, a key and a function, applies the function to the value under that key, and returned an updated map (as @noisesmith demonstrated)

hmaurer22:09:43

In your case, you had a vector of maps that you wanted to update. Clojure core has the function map which applies a function to every entry in a vector, and returns an updated vector

hmaurer22:09:40

What you wanted to do was to update a key for each map in your vector, so it’s quite natural to use the function map to then call update on each entry

hmaurer22:09:59

This probably doesn’t always apply, but when manipulating data like this try to think of what you would do to update the “most nested piece” (here, maps), and then try to think how you would apply this operation if the piece you want to update is nested in a vector, or another map, etc

hmaurer22:09:16

You might also want to check out https://github.com/nathanmarz/specter. It’s by no mean necessary to manipulate simple datastructures, but it might give you some insights on how to deal with immutable data

hubert22:09:48

@fabrao try to use

->
and
map

hmaurer22:09:55

In particular, I think the concept of “navigation” is quite helpful, even when thinking about mapping over a vector of maps, like you did

noisesmith23:09:26

NB for “navigation” you can check out zippers, which literally let you represent your traversal and updates as an arbitrary series of navigations of the data

nathanmarz23:09:47

zippers add a lot of overhead, whereas specter navigators generally run with near-optimal performance