beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
2020-12-15T00:03:31.195600Z

Using next-jdbc and hikari-cp to connect to my Postgres database, when I query a row, I’m getting an #inst "2020-08-14T00:26:30.000000000-00:00" on the timestamp value. I’m trying to get it to transform the value to an OffsetDateTime. This is my row builder.

(jdbc/execute-one! @data-source 
                   query
                   {:builder-fn (result-set/as-maps-adapter
                                 result-set/as-unqualified-maps
                                 result-set/read-column-by-index)
                    :return-keys true}

2020-12-15T00:04:01.196Z

When I query, it gives me this value now

#object[com.zaxxer.hikari.pool.HikariProxyResultSet 0x45deee29 "HikariProxyResultSet@1172237865 wrapping org.postgresql.jdbc.PgResultSet@97151ca"]

2020-12-15T00:32:03.198800Z

an #inst tagged literally is the printed form of a few different kinds of date time objects

2020-12-15T00:32:23.199400Z

so that doesn't actually tell you how to proceed to get what you want

2020-12-15T00:32:35.199900Z

but it is almost certainly a java.sql.Timestamp

seancorfield 2020-12-15T00:32:46.200300Z

@audyarandela For the builder, why are you not just using result-set/as-unqualified-maps?

2020-12-15T00:32:51.200500Z

which is a subclass of java.util.Date

seancorfield 2020-12-15T00:33:46.201300Z

result-set/read-column-by-index is not a valid second argument for as-maps-adapter so your values will not be read correctly.

2020-12-15T00:34:17.202300Z

so you just figure out how to go from java.util.Date to whatever you want

2020-12-15T00:35:09.203100Z

So I did just use as-unqualified-maps but the value in dB = 2020-12-11 16:02:57.217602 but want it to be the OffSetDateTime 2020-12-11T16:02:57.217602-06:00 . Do I just take that value and do a java-time conversion outside of next-jdbc, or can next-jdbc convert it before returning the row?

2020-12-15T00:35:48.203700Z

oh

2020-12-15T00:35:59.204200Z

you are running into a timezone issue as well

2020-12-15T00:36:09.204700Z

Yes, I hate working with time lol

2020-12-15T00:36:31.205500Z

you are storing dates without a timezone, and your timezone and your databases configured timezone don't agree

seancorfield 2020-12-15T00:36:44.206Z

The answer is complicated. #sql would be the best channel to follow-up in. Yeah, timezones are hard 😞

2020-12-15T00:37:10.206700Z

you are going to have a bad time, because if I recall most jdbc drivers default to assuming a date is in utc if no timezone is stored

😅 1
2020-12-15T00:37:56.207400Z

Yea thats starting to make more sense now. Thank you both for the replies!

seancorfield 2020-12-15T00:38:13.207800Z

PostgreSQL has both timestamp and timestamp-with-tz types (making the problem worse, in my opinion).

seancorfield 2020-12-15T00:39:24.209300Z

The only sane approach with databases and timezones is to have your server set to UTC, your JVM set to UTC, and your database set to UTC, and to work entirely in UTC, converting to/from zoned-times just at the edges. And even then it's actually a bit more complicated than that.

👍 1
😅 1
2020-12-15T00:40:34.209500Z

aye yi yi

2020-12-15T00:41:19.210Z

Ok, i’ll have to go back to the drawing board. Thanks again!

seancorfield 2020-12-15T00:43:10.211200Z

There's a next.jdbc.date-time namespace that might help you a bit, if you're not already requiring that -- see the Tips & Tricks page for the PostgreSQL section (it's also briefly mentioned near the bottom of the Getting Started guide).

👍 1
Timur Latypoff 2020-12-15T05:40:24.214400Z

I like storing and manipulating all dates as long Unix time stamps with milliseconds (always UTC), it saves from lots of troubles.

seancorfield 2020-12-15T05:44:54.214600Z

Yeah, I've seen that as advice for dealing with dates/times. It seems a bit extreme when you can have the affordance of actual date/time values in UTC.

Timur Latypoff 2020-12-15T05:50:44.219400Z

Yeah, I guess if the SQL queries use date functions, it’s in many ways beneficial to use proper date types. Funnily though, from my experience in finance, every time a developer doesn’t store dates as Unix time stamps, someone’s going to stay up at night at least twice a year, when summer/winter time change.

😂 1
seancorfield 2020-12-15T06:05:17.219700Z

Aye, finance is its own peculiar world...

2020-12-15T10:03:04.224500Z

I’m soon done with Brave and True. A great and fun introduction to the language! But I would now like read a book that’s a bit more direct and demanding in terms of learning Clojure. Possibly aimed at experienced programmers who aren’t new to immutability, FP etc but just to LISPs. Any good recommendations? I’m especially experienced with C# so any “Clojure for Java devs” type book would probably work well too.

clyfe 2020-12-15T10:15:21.224800Z

https://clojure.org/community/books

clyfe 2020-12-15T10:15:38.225Z

Try https://a.co/bSHZ7X3

dharrigan 2020-12-15T10:42:43.225200Z

https://pragprog.com/titles/shcloj3/programming-clojure-third-edition/ is a very good choice

dharrigan 2020-12-15T10:43:25.225500Z

`https://pragprog.com/titles/roclojure/getting-clojure/ too

dharrigan 2020-12-15T10:43:25.225700Z

🙂

dharrigan 2020-12-15T10:44:45.226Z

For a very very good explaination of each of the core functions, the standard library so-to-speak, then this book <https://www.manning.com/books/clojure-the-essential-reference> is , well, essential! 🙂

2020-12-15T10:58:02.226200Z

Thanks a lot!

okwori 2020-12-15T12:01:51.227400Z

//**foo.js**

$(document).ready(function ($) {
initBarD();
 //...
});

//**bar.js**

function initBarD() {
  $('.aclass').on('click', function () {
    $(this).toggleClass('is-active');
    $(this).find('.cm').slideToggle();
  });
 //...
}
** index.html for cljs(reagent/re-frame) code **
&lt;body&gt;
&lt;div id="app"&gt;&lt;/div&gt;
 &lt;!-- ... --&gt;
 &lt;div class="aclass"&gt;&lt;div class="cm"&gt;&lt;/div&gt;&lt;/div&gt;
 &lt;!-- ... --&gt; 
 &lt;script src="/js/bar.js"&gt;&lt;/script&gt;
 &lt;script src="/js/foo.js"&gt;&lt;/script&gt;
 &lt;script src="/js/app.js"&gt;&lt;/script&gt;
&lt;/body&gt;

okwori 2020-12-15T12:01:55.227600Z

For some reason the method initBarD is not working. I tired to set extern but still same thing....

2020-12-15T12:24:05.229200Z

Is defrecord considered good practice? I was kind of surprised to see it in the language. Not even sure why. Just felt a bit... type-y?

borkdude 2020-12-15T12:36:35.230600Z

@anders152 defrecord is usually a way to get good performance for often used keys, while also having the flexibility of adding other keys. Also you can implement protocols with it, unlike with regular maps. Well, you can now, via metadata

borkdude 2020-12-15T12:38:20.231100Z

https://clojure.org/reference/datatypes

Timur Latypoff 2020-12-15T13:11:38.233200Z

I see defrecord as one of the many examples for Clojure being a very practical language. Like, yeah, this is a "dirty" performance trick tied to internals of JVM, but sometimes you need this performance.

Eric Ihli 2020-12-15T13:31:03.239Z

What's a reasonable way to get map-like behavior in a more memory efficient way? I've got a map that serializes to 140mb. When I deserialize it into memory, it takes up 1.5gb due to the overhead of a hash-map. The only behavior I need is index lookup. The keys are strings, the value are maps of depth one. The reason for the value being a map is mostly convenience. It could be a pair of lists that I could manually seq through if that would be more memory efficient. I don't need O(1) lookup on the value. I do need O(1) lookup on the outermost key/value pairs. I thought about maybe using something like SQLite and looking up from disk with an added LRU cache. The data is basically a dictionary of English words, so for performance, I'll mostly be hitting the same values the most often. Is there an alternative anyone else knows of?

{("profanity" "unholy") {"its" 2},
 ("ants" "triumph") {nil 1},
 ("hiding" "our") {"of" 1, "expose" 3, "above" 1},
 ("won't" "intervention") {"divine" 1, "an" 1},
 ("pines" "weeping") {"the" 1},
 ("let" "give") {"to" 1},
 ("memory" "undead") {"an" 1},
 ("waters" "one") {"as" 1},
 ("that" "palms") {"the" 1, "these" 1},
 ("you" "tonite") {"but" 1, "volume" 1}
 ,,,
}

Eric Ihli 2020-12-15T13:33:28.239900Z

I tried using defrecord for the inner values. That resulted in worse memory size. Instead of {("hiding" "our") (hash-map "of" 1 "expose" 3,,,)} it was {("hiding" "our") (-&gt;MyRecord ["of" "expose" ,,] [1 3 ,,,])

2020-12-15T13:35:53.240200Z

Do the same words occur many times, e.g. "the" appears thousands of times?

Eric Ihli 2020-12-15T13:36:26.240500Z

Yes. Very much so.

2020-12-15T13:36:52.240800Z

The reason I ask is that the default way of reading such a data structure will cause each occurrence of "the" to be a different JVM object allocated in memory.

2020-12-15T13:37:26.241100Z

If they were Clojure keywords, that would not be the case, because each Clojure keyword, for time efficiency sake (but also it can help with memory efficiency) is guaranteed to be the same JVM object in memory.

Eric Ihli 2020-12-15T13:37:29.241300Z

Ah. That was a question I had and I assumed all of the references of "the" pointed to the same spot in memory.

2020-12-15T13:38:27.241500Z

I do not know of an off-the-shelf way to change the behavior of a typical Clojure data reader to "intern" strings the way that is done for keywords, without doing some code changes inside of those data readers.

2020-12-15T13:38:58.241700Z

"intern" being a verb that is often used for this process of trying to reuse the same identical object when it appears again.

2020-12-15T13:39:38.241900Z

It also is not clear exactly how much memory it would save, but perhaps you would be interested in a quick experiment to find out?

Eric Ihli 2020-12-15T13:39:43.242100Z

Excellent. I will try just using keywords for everything and converting them back to strings before display.

2020-12-15T13:40:20.242300Z

Be aware that the set of characters that can appear in a keyword, and be print/read-round-trippable is smaller than for strings.

2020-12-15T13:41:15.242500Z

if they are all letters, you are fine. It is characters like : ' / that can cause troubles in reading (and others)

Eric Ihli 2020-12-15T13:51:06.242700Z

The footprint difference is 400mb. 1.9g to ~1.5g. That's awesome. I think I'll still need more savings on top of that. (name (keyword "won't")) -> "won't" works fine. I think that's the only one in my data.

Timur Latypoff 2020-12-15T14:10:46.242900Z

I believe (String/intern x) does interning on arbitrary strings if it helps, without a need for keywords. https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern%28%29

Timur Latypoff 2020-12-15T14:13:17.243200Z

Also, (array-map ...) should be more memory-efficient than (hash-map ...) with its lookup complexity tradeoffs, but we're going into deep byte-hoarding territory. https://clojuredocs.org/clojure.core/array-map

2020-12-15T14:18:16.243800Z

I've done some experiments, and reading Eric's data using the standard Clojure reader produces array maps for most of them already, since most of his maps are small.

👍 1
2020-12-15T14:22:49.244100Z

Hmmm, and it seems that forcing the creation of array maps with large numbers of keys is O(n^2) (guess on my part at the moment, but would make sense if so), so not recommended for maps with 90,000 entries 🙂

2020-12-15T14:24:24.244300Z

Yep, it is O(n^2), to check for duplicate keys. As expected, array maps are good for small maps, not huge ones.

Ben Sless 2020-12-15T14:49:14.244700Z

what about -XX:+UseStringDeduplication ?

2020-12-15T15:29:31.244900Z

As an interesting curiosity, both vim and Emacs take more memory to load that file than Clojure does 🙂

🙃 1
2020-12-15T15:29:57.245200Z

Probably because of some mode that the .edn suffix causes them to invoke, not sure.

2020-12-15T15:42:25.245500Z

@ericihli Most of your maps are small, so they are by default implemented in Clojure as an array map, not a hash map. The memory overhead for an array-map with 1 key in it (which most of your maps have) is 56 bytes. For 1 million such maps, the memory overhead is about 56 MBytes. That doesn't sound like where most of the memory is coming from.

Eric Ihli 2020-12-15T15:51:41.245700Z

2.8 million keys. Each key is a list of 2 strings (or keywords). Each value is an array-map (or hash-map) with an average of 3 kv pairs of string (or keyword) -> int.

2020-12-15T15:52:24.245900Z

yeah, you asked something similar before and shared your data file, which I still have a copy of and have been experimenting a bit more with this morning. 2.8 million 1-key array-maps takes about 150 MBytes of RAM.

2020-12-15T15:52:52.246100Z

for just the part that is "overhead", that is, over and above the memory required to represent the key and the value

2020-12-15T15:53:59.246500Z

The average might be 3, but I see 2 million out of those 2.8 million with 1 key/value pair

2020-12-15T15:54:58.246700Z

Try this (if your data is in x1) and you will see how few of them are hash maps: (frequencies (map class (vals x1)))

Eric Ihli 2020-12-15T15:56:03.246900Z

Ah thanks. Just came across this that I'm going to try too. > -XX:+UseCompressedOops Enables the use of compressed pointers (object references represented as 32 bit offsets instead of 64-bit pointers) for optimized 64-bit performance with Java heap sizes less than 32gb.

2020-12-15T15:56:56.247100Z

I thought that was the default if your max heap is under something like 16 GBytes

Eric Ihli 2020-12-15T15:57:17.247300Z

Ah. I didn't know that.

2020-12-15T15:58:05.247500Z

The cljol library I wrote can help visualize small data structures in a JVM, and quickly show whether references are 4 or 8 bytes in size. Don't try it on your whole data structure or it will run out of memory.

2020-12-15T15:58:31.247700Z

https://github.com/jafingerhut/cljol

Eric Ihli 2020-12-15T16:02:10.248100Z

Is this the right way of thinking about memory size by reading the code? https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentList.java

private final Object _first;
private final IPersistentList _rest;
private final int _count;
2 pointers (4 bytes each?) + 1 int (another 4 bytes?). https://github.com/clojure/clojure/blob/0df3d8e2e27fb06fa53398754cac2be4878b12d1/src/jvm/clojure/lang/ASeq.java#L16
transient int _hash;
transient int _hasheq;
+ 2 ints https://github.com/clojure/clojure/blob/0df3d8e2e27fb06fa53398754cac2be4878b12d1/src/jvm/clojure/lang/Obj.java#L17
final IPersistentMap _meta;
+ 1 pointer

2020-12-15T16:02:54.248300Z

yes, and cljol can show you the exact layout of the fields in memory in your running JVM, too.

2020-12-15T16:03:32.248500Z

Here is a gallery of images created using it (again, on small data structs) demonstrating compressed OOPS, and also 1-byte-per-char strings that might help you, if you are not already taking advantage of it: https://github.com/jafingerhut/cljol/blob/master/doc/README-gallery.md

2020-12-15T16:04:36.249Z

What JDK are you using? If 8, then all strings are consuming 2 bytes per char in memory, even if they are ASCII. JDK 9 and later can store ASCII strings in 1 byte per char: https://github.com/jafingerhut/cljol/blob/master/doc/README-gallery.md#compact-strings-in-java-9-and-later

Eric Ihli 2020-12-15T16:05:11.249400Z

OpenJDK 1.8

2020-12-15T16:05:37.249600Z

It might not reduce memory a lot for you, since most of your strings are probably short, and it will not eliminate the per-string overhead of JVM objects, which is noticeable.

2020-12-15T16:06:12.249800Z

The most it could save would be the size of your file, and it probably won't save that much.

2020-12-15T16:13:48.250Z

Are you trying to reduce this memory usage because 1.4 GB is too much RAM, for some reason? Or because you want to scale this up to 10x or 100x larger data sets?

Eric Ihli 2020-12-15T16:18:05.250200Z

Started off as a toy project that I wanted to be able to run on a $5 digitalocean instance with 1g of ram. Not worth paying $40/mo. I think I can get by using swap, but it results in ~10 minute startup times for the app. Haven't yet seen how it affects running performance but I imagine it will be unbearable. On top of that, this is a minimum set of data and I want to know how I could scale it if needed.

Eric Ihli 2020-12-15T16:35:17.250500Z

Just looking at a random sampling of MapEntries using cljol it looks like they are averaging ~400 bytes each. Lots of overhead from _meta, _hash, _count, _first, _rest , and of course the strings although those could be interned or keywordized. But I'm realizing a lot of that is just for human readability. The keys can be just hashes in memory as long as I have a way to eventually convert the hashes to strings. I think I'm going to have 1 file that is a map of

{(hash '("foo" "bar") {(hash "buzz") 1 (hash "bazz") 3}
 ,,,}
And then have an on-disk database of hashes to human things.

2020-12-15T16:47:30.250700Z

There are no MapEntry objects that exist when creating a map, I believe, only when doing something like seq on a map.

2020-12-15T16:47:43.250900Z

I'd recommend using something like cljol to determine which objects are actually taking up memory.

2020-12-15T16:49:09.251100Z

but yes, in general, there are fields in there that you do not use in your application, very likely.

Eric Ihli 2020-12-15T16:49:43.251300Z

Ah. So I was doing

(require '[cljol.dig9 :as d])
(def m (into {} (take 5 dl/darklyrics-markov-2)))
(d/view m)
But that's the only reason I am seeing MapEntry objects, is because the take is "like seq"?

2020-12-15T16:50:53.251600Z

Store it directly in a array of bytes as a trie, no need to serialize/deserialize, size on disk directly matches size in memory

2020-12-15T16:58:02.251800Z

It is a little weird, I know, but you should do (d/view [m]) (it takes a sequence of top-level objects, all drawn in gray)

2020-12-15T16:58:18.252Z

(to make it easier to see sharing of memory between multiple root data structures, when it exists)

2020-12-15T16:58:50.252200Z

Because you did (into {} (take 5 ...)) the result should be a map. I do not see MapEntry objects in the resulting data structure when I do that.

🙏 1
Eric Ihli 2020-12-15T17:00:52.252400Z

@hiredman I'm trying to parse what you said into what you mean and my brain's throwing exceptions. Are you saying instead of:

{("foo" "bar") {"baz" 1 "buz" 3}
 ("foo" "biz") {"baz" 2 "buz" 5}
 ,,,}
rather:
{"foo" {"bar" {"baz" 1 "buz" 3}
        "biz" {"baz" 2 "buz" 5}}
 ,,,}
That's my understanding of trie from a quick google search. Instead of
{"abc": ,,,
 "abd": ,,,
 "abe": ,,,}
rather
{"a" {"b" {"c" ,,, "d" ,,, "e" ,,,}}} 

2020-12-15T17:08:46.253900Z

I guess

2020-12-15T17:09:10.254500Z

you can have a trie and different levels depending, the bit level, the byte level, the character level, etc

2020-12-15T17:10:17.255100Z

if you are committed to just lowercase english words, you could do a trie at the letter level, so you would have a tree with 26 way branches

Ricardo Cabral 2020-12-15T17:10:58.255600Z

(defn operation2 [f &amp; args] (apply f args))

(operate2 str "It " "should " "concatenate" ) ;; it works
(operate2 + [1 2 3] ) ;;not working
(operate2 + 1 2 3 ) ;;not working
Hello all, I am learning the basics of Clojure and there is a small exercise with I did not understand, could someone let me know why the function operate2 does not work with “+” function and it works with the “str”, considering that both are functions, it should work, shouldn’t it?

2020-12-15T17:11:34.255700Z

a trie directly in a byte array is mostly useful as a static lookup table, not so useful for doing updates on

Eric Ihli 2020-12-15T17:20:38.256700Z

Ah. I see. https://www.aclweb.org/anthology/W09-1505.pdf#page=2 The figure on the right of that page makes sense.

pyry 2020-12-15T17:22:24.257100Z

It should work alright. I notice that you have defined operation2 in the defn block while what you're calling in the examples is actually operate2 - any chance there's an old function definition lurking behind operate2 ?

✅ 1
Ricardo Cabral 2020-12-15T17:25:58.257400Z

You are right. Thank you very much. it is time to take a break 🙂. too much information. Just a quick question. Now it works if I pass the numbers (operation2 + 1 2 3) but it does not work when I pass a vector (operation2 + [1 2 3]). Should it work passing a vector?

dorab 2020-12-15T17:27:57.257600Z

No. Because that would be the equivalent of (+ [1 2 3) which would not work.

👍 1
pyry 2020-12-15T17:28:03.257800Z

It shouldn't. The &amp; args passes the rest of the arguments to the method in wrapped in a collection, so (operate2 + [1 2 3]) would end up doing something like (apply + [[1 2 3]]) .

Ricardo Cabral 2020-12-15T17:33:18.258100Z

understood Thank you so much dorab and pyry

Joaco 2020-12-15T17:51:31.258500Z

Hi, I have a REPL question I started a REPL and trying to do a http request with HttpClient and repl throws java.lang.IllegalStateException: Client/Server mode has not yet been set. but if I build the jar and run it’s works fine Do I need run REPL with some kind of flag or something?

2020-12-15T17:59:46.259100Z

That sounds like a problem I was having recently where there was a version incompatibility between a couple of libraries I was using.

2020-12-15T18:00:38.259600Z

If I remember rightly, I fixed it by upgrading to http-kit 2.5.0

🙌 1
Joaco 2020-12-15T18:05:26.261500Z

😮 @manutter51 yes! I updated to 2.5.0 and the problem has gone thank you!

👍 1
Miguel Soares 2020-12-15T18:06:34.262700Z

I’m building a function that will conj to a list of maps but if there is a duplicated entry it should replace it. I got it working but it looks ugly 😞 What is the idiomatic way of solving this? Is there a function that already does this?

(defn conj-on-conflict [state new-map]
  (let [id         (:id new-map)
        maps       (:maps state)
        duplicated (first (filter #(= (:id %) id) maps))
        maps       (if duplicated
                     (reduce (fn [accum map]
                               (if (= (:id map) id)
                                 (conj accum new-map)
                                 (conj accum map)))
                             [] maps)
                     (conj maps new-map))]
    (assoc state :maps maps)))

(comment
  (let [state       {:maps [{:id 1 :val 1}]}
        new-map     {:id 1 :val 2}

        empty-state {:maps []}]
    (conj-on-conflict state new-map)                        ;=&gt; {:maps [{:id 1, :val 2}]}
    (conj-on-conflict empty-state new-map)                  ;=&gt; {:maps [{:id 1, :val 2}]}
    )
  )

2020-12-15T18:10:47.263600Z

@miguel994 Does the order in the list matter? Otherwise you can use a set to solve your problem

Miguel Soares 2020-12-15T18:12:24.265200Z

No it doesn’t, can you elaborate? duplicated in this case means that it has the same id.

2020-12-15T18:13:38.266300Z

I see, so not the entire entry. That's what a set is; an unordered collection of entries that is guaranteed to contain no duplicates (and provides very fast lookup for contains?)

2020-12-15T18:13:57.266700Z

In that case, why not use a map that contains the other maps?

2020-12-15T18:14:07.267Z

Using the ids as keys

Miguel Soares 2020-12-15T18:16:46.268900Z

That would require to transform the data to be a map of maps and then transforming it back again to a list of maps, which is fine I guess .

2020-12-15T18:18:31.269600Z

Depends on what you want to do with them! What's the use case?

Miguel Soares 2020-12-15T18:18:43.270Z

I would like something like INSERT ON CONFLICT from postgres.

Miguel Soares 2020-12-15T18:19:26.270100Z

I want to assoc back to the state which has a spec that requires a list of maps

2020-12-15T18:19:59.270300Z

As a general rule, optimizing down to the lowest constant factors at the bit level is the realm of C/C++/assembly, in my opinion, not the JVM. There are certainly more memory-dense data representations than Clojure's that can be done on the JVM, too. Clojure's built-in data structures are not really optimized for minimum memory, more so for fast "effectively constant time" lookups and modifications, and for generality in the types that can be stored in keys/values/vector-elements/set-elements, which has some cost in memory, versus creating specialized structures that can only hold 32-bit integers, for example.

2020-12-15T18:20:49.270500Z

The biggest hammer you have for reducing memory usage by a factor of 4 or more would be looking for a Java library that does what you want, where memory usage has been taken into account in its design.

2020-12-15T18:21:08.270700Z

Hmmm, ok. What I'm curious about is why it has to be a list? I would say that the idiomatic way to solve this "overwrite entry by id" problem is to use a map

2020-12-15T18:21:31.270900Z

Often in Clojure data structure choice is a big part of problem solving

Miguel Soares 2020-12-15T18:23:12.271100Z

Yes, probably i’m just being stubborn. 😅 Thanks for the inputs

2020-12-15T18:24:53.271300Z

A cool thing about Clojure is that most collection data structures implement seq, meaning you can use map, filter, reduce etc on them without any modifications! So if the order doesn't matter, you'll most probably be fine with a map if you iterate over the state later in your program.

Renato Alencar 2020-12-15T18:26:17.271500Z

You could map the sequence of maps replacing things with the same id, it would be more general and avoid a lot of code.

Renato Alencar 2020-12-15T18:28:15.271700Z

Something like:

(defn conj-on-conflict
  [{:keys [maps] :as state} {:keys [id] :as new-map}]
  (assoc state
         :maps (map #(if (= (:id %) id)
                       new-map
                       %)
                    maps)))

Renato Alencar 2020-12-15T18:28:41.271900Z

Probably, a little bit more organized than that

Renato Alencar 2020-12-15T18:32:17.272300Z

Actually, you would have to add a step verify duplicates, but that could be done with some instead of filter, since some would return when it finds the first true.

Miguel Soares 2020-12-15T18:33:19.272500Z

What if maps its a empty list?

Miguel Soares 2020-12-15T18:33:34.272700Z

then it will not conj to the list

Renato Alencar 2020-12-15T18:34:06.272900Z

So, instead of (first (filter #(= (:id %) id) maps)) , you could use (some #(= (:id %) id) maps).

Renato Alencar 2020-12-15T18:39:06.273200Z

Sure, I missed that. Wait a sec.

Renato Alencar 2020-12-15T18:58:40.273400Z

You could convert your map sequence to a map, where the keys would be the ids and the values would be the maps. Then you assoc the new-map using its id as key. Now, if you do a vals in this final map, you will have a sequence of maps with no duplicates.

Renato Alencar 2020-12-15T19:14:28.273800Z

(defn conj-on-conflict
  [{:keys [maps] :as state} {:keys [id] :as new-map}]
  (assoc state
         :maps (vals
                (assoc (into {} (map #(vector (:id %) %) maps))
                       id new-map))))

2020-12-15T20:07:55.275500Z

I'm going through some Advent of Code days just to get a feel for Clojure (and loving it so far). Tried rewriting a solution using -&gt;&gt; but keep stumbling into an issue. This is the code:

(-&gt;&gt; "day1.txt"
     io/resource
     slurp
     str/split-lines
     (map #(Integer. %))
     #(for [l1 % l2 % l3 %] [(+ l1 l2 l3) (* l1 l2 l3)]))
If I remove the last function with a list comprehension everything works as expected. But that line makes my program complain about:
Execution error (IllegalArgumentException) at test.day1/eval1660 (day1.clj:12).
Don't know how to create ISeq from: test.day1$eval1660$fn__1667
I'm sure I'm missing something obvious here...

2020-12-15T20:10:42.276900Z

@anders152 #(...) expands to (fn [..] ...), so you get (fn [...] (for ...) &lt;result of computation&gt;)

2020-12-15T20:10:53.277200Z

Ah. I need to wrap it in parens?

2020-12-15T20:11:05.277600Z

that works but isn't what most people would recommend

2020-12-15T20:11:17.278Z

I'm all ears! 😄

2020-12-15T20:11:35.278400Z

usually we restructure, eg. putting the rest of the chain inside the binding block of the for

2020-12-15T20:11:50.278800Z

or making a function with a name calling for, and putting that in the chain

2020-12-15T20:12:10.279300Z

Second one I understand - first one... could you show me an example?

2020-12-15T20:12:18.279600Z

Or link me to something to read on it

2020-12-15T20:13:18.280500Z

in your case you'd need let first since you use the same input multiple times

2020-12-15T20:13:56.281400Z

but in the normal case (for [x (-&gt;&gt; ...)] ...) - instead of for being inside ->>, ->> can be inside for

2020-12-15T20:14:15.282Z

What about something like this?

2020-12-15T20:14:16.282100Z

Ah! I understand. I'll just define a separate function in this case. Definitely makes it easier to read too.

2020-12-15T20:14:19.282300Z

(let [list-comp #(for [l1 % l2 % l3 %] [(+ l1 l2 l3) (* l1 l2 l3)])]
  (-&gt;&gt; "day1.txt"
       io/resource
       slurp
       str/split-lines
       (map #(Integer. %))
       list-comp))

2020-12-15T20:14:45.282700Z

even better, no need to do a def of course

2020-12-15T20:14:58.283200Z

once it has a name, I'd no longer use #() and a local binding to define it

☝️ 1
2020-12-15T20:15:50.284200Z

That was just a quick refactor to get around the quirky interaction between -&gt;&gt; and #()

2020-12-15T20:16:05.284700Z

Normally I’d go for a defn.

2020-12-15T20:16:27.285200Z

right, while we are reviewing the code, I'd use #(Long/parseLong %) instead of #(Integer. %) too

bnstvn 2020-12-16T21:47:08.379800Z

I’ve seen (read-string %) variants for parsing ints a couple of times — is it general?

2020-12-16T21:58:53.381Z

it works for longs, it also has the ability to execute arbitrary code, or create arbitrary object types

👍 1
2020-12-16T21:59:12.381200Z

also, of course, #(read-string %) can be replaced by read-string

2020-12-15T20:16:50.285800Z

nice, thanks!

2020-12-15T20:16:54.286Z

it's more specialized, and gives the same numeric datatype you'd get for a standard literal in a file

2020-12-15T20:16:58.286200Z

any other things that jump out just let me know

2020-12-15T20:18:24.287Z

also [(+ l1 l2 l3) (* l1 l2 l3)] can be simplified to ((juxt + *) l1 l2 l3)

2020-12-15T20:19:35.287400Z

user=&gt; ((juxt + *) 2 3 4)
[9 24]

2020-12-15T20:21:04.288Z

Interesting, reading on juxt now. Looks useful and powerful

2020-12-15T20:21:28.288400Z

my experience is its fun, and a delight to find uses for, but relatively rare :D

2020-12-15T20:22:00.289Z

Definitely fits the "I want to apply a series of functions to a series of values and get the summary of all the function executions in a nice format" type problem...

phronmophobic 2020-12-15T20:23:13.289900Z

I use juxt mostly with map

(map (juxt :id :name) some-hashmaps)

2020-12-15T20:23:42.290600Z

I like it for sort-by as well

👍 2
2020-12-15T20:24:12.291200Z

instead of #([(:id %) (:name %)]) ?

phronmophobic 2020-12-15T20:24:42.291800Z

instead of #(vector (:id %) (:name %))

2020-12-15T20:24:44.291900Z

that's invalid, it turns into calling a [] with zero args, which blows up

2020-12-15T20:25:05.292300Z

oh right, that was even mentioned as a gotcha somewhere...

2020-12-15T20:25:44.293500Z

#() and ->/->> are much weirder than they first seem, because they operate in the realm of syntax transforms, not program logic

2020-12-15T20:26:07.293900Z

Yeah, I mostly wanted to see if I understood them correctly. Not sure they really fit my solution here

2020-12-15T20:26:23.294500Z

user=&gt; (-&gt;&gt; (+ a b) (let [a 19 b 23]))
42

👀 1
😱 2
2020-12-15T20:27:09.295200Z

I do have one more question. What's the standard way of getting values out of a map when passed to a function? For vectors I can obviously just match on the position, but on maps?

2020-12-15T20:27:54.296200Z

you can destructure using :keys, :syms, :strs or the full map destructure syntax {var "some-key"}

➕ 1
2020-12-15T20:28:16.296400Z

the full guide to destructuring https://clojure.org/guides/destructuring

2020-12-15T20:28:52.296800Z

Thank you, will read

Miguel Soares 2020-12-15T20:54:57.297300Z

Yes, that makes a lot of sense and its way better than what I had, thank you 🙏

Renato Alencar 2020-12-15T20:55:49.297500Z

I'm glad to help

2020-12-15T21:00:10.298100Z

is it possible to have one arity of an anonymous fn call another arity? what name do you use in that situation?

2020-12-15T21:01:01.298700Z

the arity itself solves it (you need a direct call by name, recur doesn't work)

Renato Alencar 2020-12-15T21:01:18.299200Z

And, I just saw another discussion below, and noticed something. #(vector (:id %) %) could actually be replaced by only (juxt :id identity)

2020-12-15T21:01:31.299600Z

you can give an anonymous fn a name like this: (fn fn-name ([x] ...) ([x y] ...))

2020-12-15T21:01:41.299900Z

aha, thanks

2020-12-15T21:01:58.300300Z

giving it a name that way also makes stack traces a bit more useful

2020-12-15T21:45:28.300800Z

Is it possible to tell the clojure command line app where your deps.edn file is?

2020-12-15T21:46:59.302Z

(If say, you want to run clojure from a different directory. Maybe something like:

clojure -d ./sub-dir/deps.edn -m cljs.main --compile foo

2020-12-15T21:47:23.302600Z

Or do I have to do something like:

(cd sub-dir &amp;&amp; clojure -m cljs.main --compile foo)

2020-12-15T21:47:28.302900Z

?

2020-12-15T21:47:40.303300Z

I think it's easier to cd to the dir with the deps.edn, and tell it your source tree is on some other path, yeah

2020-12-15T21:48:15.303700Z

but maybe there's a new option for specifying a deps file, since I last checked

2020-12-15T21:49:57.304Z

@noisesmith Sad, okay, thanks. 🙂

alexmiller 2020-12-15T21:50:24.304300Z

no, there's not

clyfe 2020-12-15T22:17:08.304700Z

CLJ_CONFIG=/path/to/dir_with_deps_edn clj ...
May help

clyfe 2020-12-15T22:18:32.304900Z

But that's the user deps, not project