Fork me on GitHub
Endre Bakken Stovner06:08:39

Why do we need special syntax for JS properties in cljs? Java has attributes and we need no special syntax for them, right?

Endre Bakken Stovner08:08:31

I am guessing it is because fields and methods seem identical in JS, but I have never seen it explained 🙂


AFAIU, they don't just seem identical, they are identical. So, (.methodName obj) translates to obj.methodName() (a method call), and (.-methodname obj) translates to obj.methodName (not a call, returns the method itself)

Endre Bakken Stovner09:08:44

Good to know! Thanks 🙂

Alex Miller (Clojure team)12:08:30

In Java, . finds either method or field (method preferred) and .- finds only field

🎵 3
👍 3
Jeff Evans14:08:37

What’s the opposite of dissoc? assoc doesn’t quite seem like it, because you have to provide new key/value pairs. I’m looking to keep only certain keys from the map (without having to care about what their current values), while maintaining the order in the map.


Maybe you want select-keys?

Jeff Evans14:08:27

select-keys actually throws away the order, if you give it an clojure.lang.PersistentArrayMap (which is ordered). Sorry, should have clarified that part as well.

Jeff Evans14:08:32

i.e. it constructs a new map


I think that trying to preserve the order is a category error of sorts

☝️ 3
Jeff Evans14:08:07

That’s entirely fair. I’m working on a patch for a certain library that is specifically asking to preserve the order. But I agree with what you’re saying. Order shouldn’t matter, generally, for maps.


is the library public?

Jeff Evans14:08:50

I realize I can basically just build up a new array, like this, just wondering if there’s a slightly simpler way:

Jeff Evans14:08:27

although, in truth, some testing of small maps with select-keys suggests that it does preserve the order anyway

Jeff Evans15:08:13

or at least, it keeps the order of the keys you have passed, but only recently (i.e. the first example shown here is no longer what I see on 1.7.0):

Daniel Stephens15:08:02

At some point, I believe for performance there was a change such that maps start as PersistentArrayMaps and change to PersistentHashMaps after the 8th assoc'd element.

✔️ 3
Daniel Stephens15:08:06

(select-keys (apply sorted-map (interleave (range 20) (range 20))) (range 10))
=> {0 0, 7 7, 1 1, 4 4, 6 6, 3 3, 2 2, 9 9, 5 5, 8 8}

Daniel Stephens15:08:48

for comparator based order something like this would work

(let [inheriting-select-key (fn [map keyseq]
                              (into (empty map) (select-keys map keyseq)))]
  (inheriting-select-key (apply sorted-map (interleave (range 20) (range 20)))
                         (range 10)))
=> {0 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7, 8 8, 9 9}
but if you want to maintain insertion ordered maps then you'd need to traverse the whole map and filter for the keys I suppose which could be a performance concern for a library like specter

Jeff Evans15:08:04

thanks, Daniel, much appreciated. I have a lot to chew on now

👍 3

If you want a sub-map kind of operation that preserves the concrete type of the given map, e.g. PersistentArrayMap or sorted-map, there is nothing in Clojure core lib that does that. It would be a bit fiddly to write one, but not terribly difficult.

Jeff Evans15:08:00

using a protocol based approach, right?


It would not be difficult for existing concrete map types built into Clojure, that is. If you pass it some other map type from a 3rd party library, e.g. a priority-map or ordered-map, then there is no 'sub-map' interface/protocol/whatever to make it straightforward to extend a sub-map operation to new concrete map types one at a time, independently.


I suppose you could create your own protocol for this, implement it for the concrete types built into Clojure core, and invite others to implement it for their custom map types.

Jeff Evans15:08:02

I suspect that for this particular case, falling back to the behavior of select-key for non-core map types would be OK

Jeff Evans15:08:09

but thanks for confirming the general approach

Jeff Evans18:08:19

this is interesting… apparently the cutoff is ten elements, and it hinges on the to-be-selected keys, not the map to select against.

(select-keys (apply sorted-map (interleave (range 20) (range 20))) (range 9))
=> {0 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7, 8 8}
(select-keys (apply sorted-map (interleave (range 20) (range 20))) (range 10))
=> {0 0, 7 7, 1 1, 4 4, 6 6, 3 3, 2 2, 9 9, 5 5, 8 8}

Jeff Evans18:08:52

I will admit to being completely flummoxed by that. the sorted-map, of course, produces clojure.lang.PersistentTreeMap in both cases, and the (range n) produces clojure.lang.LongRange in both cases. (source select-keys) doesn’t immediately reveal any reason for the difference in behavior here

Daniel Stephens18:08:16

hmm, I'm on 1.10.0 and get a different (unordered) answer when picking 9

(select-keys (apply sorted-map (interleave (range 20) (range 20))) (range 9))
=> {0 0, 7 7, 1 1, 4 4, 6 6, 3 3, 2 2, 5 5, 8 8}
The reason is that select-keys starts with a 'normal' PersistentArrayMap {} and then conj's entries into it as it finds them in the original map, the cutoff can be seen in the implementation of PersistentArrayMap, if more than 8 keys are asked to be selected then it will pass that threshold and create an (unordered) hashmap

Jeff Evans18:08:49

ah, thanks again, Daniel. I’m on 1.7.0 at this moment which explains why I don’t see this patch

👍 3

select-keys in clojure.core starts with an empty (not sorted) Clojure map, {}, and does conj on it once for each key in the final result. If you do that in Clojure/Java, it starts out as a PersistentArrayMap until it gets somewhere around 8 or 9 keys, IIRC, then the next conj will return a PersistentHashMap. It has been like that in Clojure since the beginning, modulo perhaps an off-by-one fix somewhere in the last several releases.


Small maps are by default PersistentArrayMap's, because that is fast enough at key lookup by doing a linear search, and probably a bit lower memory utilization than PersistentHashMap

Jeff Evans19:08:44

ah yes, that can be clearly seen

(conj {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8} [:i 9])
=> {:a 1, :b 2, :c 3, :d 4, :e 5, :f 6, :g 7, :h 8, :i 9}
(conj {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9} [:z 26])
=> {:e 5, :z 26, :g 7, :c 3, :h 8, :b 2, :d 4, :f 6, :i 9, :a 1}


Any tips on how to setup honeysql and next.jdbc to handle snake case and kebab case conversions between db and application?


So i can't just set the defaults and use them and instead i must copy and paste or refer to a def at the end of every call?


Look at the docs for next.jdbc/with-options


(also, there are #honeysql and #sql channels for HoneySQL and next.jdbc/`` questions, where I'm more likely to notice them than here in beginners because they're lower traffic @U0181TUEAC8)


@seancorfield thanks! I'm sorry for not reading the docs properly 😂


NP. There's a lot of docs 🙂


@seancorfield one more question is it okay to wrap every call to c3p0 pooled next.jdbc in core.async/thread?


@U0181TUEAC8 Trying to do I/O around core.async isn't a good idea in general but I'd have to know a lot more about your code to be able to answer that in any useful way.


Doing the I/O in thread means you won't block but you could also overwhelm your server and/or your database server since that's an unbounded thread pool I believe.


Asked my colleague, who does a lot more core.async than I do: he said that, yes, doing DB stuff via core.async/thread is acceptable as long as your c3p0 connection pool is configured properly (not too large) and "as long as the number of go blocks you create is bounded, and you always blocking on the return of async/thread when you spawn it, then you will only spawn a bounded number of threads"


So, several caveats but it's OK if you're careful.


(frankly, I avoid core.async as much as possible -- it has some good use cases but a lot of people reach for it when they really do not need it!)


@seancorfield im currently rebuilding my elixir stuff both in clojure with core.async and rust with tokio and evaluating them for use in prod, maybe you could share experience maintaining projects in clojure and resource usage under load?


@U0181TUEAC8 Define "under load". Are you talking about web apps, REST APIs? Most Java web servers use a separate thread per request and that's fine for most production loads.


We use Jetty, in its default embedded configuration, and our REST API and it is fine in production.


We use core.async in one server because it's based on netty-socketio so the traffic is a) very chatty and b) all continuously connected web sockets, so core.async matches that sort of pattern. But all our other server processes use plain ol' Jetty.


And we're talking 40+ online dating web sites, with thousands of concurrent users, 24x7, backed by just three Jetty instances.


@seancorfield i was talking about web sockets, rest apis, and background jobs. Thanks about the info!


I would suggest building it without core.async and see how it performs. The JVM is really well optimized and the normal Jetty-based web server approach can take a surprising amount of traffic without needing much else.


core.async adds a lot of complexity and it's much more about collaborating processes than concurrency.


Hey team, say I want to create an seq of the function f over and over, with length n

(->> (range n)
     (map (fn [_] f))) 
Is there a simpler way to write above?


(repeatedly n f ) ?


(doesn't quote work for my use-case, because I think repeatedly calls the same function over and over too, and does not accept args


I want to then do something like this:

(->> (range n)
     (map (fn [_] f))
     (apply comp) 
(so this would run (comp f f f..) n times


if it's the same value every time, then repeat


(->> (repeat n f)
     (apply comp))


see also: constantly and iterate.

❤️ 3

(nth (iterate inc 0) 9)


can I ask what is probably a really stupid question... Why when I print to my REPL:

(println "is (" x "," y ") within: (" max-x "," max-y ")")
I get spaces that i don't expect: is ( 6 , 5 ) within: ( 7 , 5 ) Why am I getting spaces between the text and the values? e.g.
(let [a "a"]
  (println a "bc"))
;=> a bc


should I be using str to build the string before printing it?


yeah if i do (println (str ,,, )) it works


println adds the spaces for convenience so you can do stuff like (println x y)


which I guess is less convenient for your example

Ian Fernandez21:08:09

@seancorfield there's some interop with clj-new with lein ? like use lein new to generate a project with a clj-new template?

Danny O’Brien21:08:39

I'd like to use this cool-looking Java library at so I've added :dependencies [[org.clojure/clojure "1.10.0" [net.sf.biweekly/biweekly "0.6.3"]] to my lein project.clj. It seems to download fine when I kick up lein repl -- but I have no idea how to use/require/import its objects so I can use them in clojure! I've tried all the variations of net.sf.biweekly that I could think of


@d.ian.b clj-new can use templates for lein, boot, and its own templates. Does that answer your question?

Ian Fernandez23:08:07

I can use a clj-new template with lein ? maybe I should ask on #leiningen

Ian Fernandez23:08:25

but thanks @seancorfield part of the question was answered =D


Leiningen only understands Leiningen templates. Sorry, I thought I'd answered that in the channel.


Boot understands both Leiningen and Boot templates.


clj-new understands Leiningen, Boot, and clj-new templates.


It doesn't make sense for Leiningen to understand a clj-new template (since it would generate a CLI/`deps.edn` project which lein could not use).


If you use clj-new with a Leiningen template, you will get a Leiningen project (unless the template also contains support for Boot and/or CLI/`deps.edn`).


If you use Boot with a Leiningen template, you will also get a Leiningen project (for the same reason).


If you use clj-new with a Boot template, you will get a Boot project (again, unless the template author has updated it to include support for CLI/`deps.edn`)


There are very few clj-new templates. clj-new has three built-in ones (`app`, lib, template) which generate CLI/`deps.edn` projects. these are all the clj-new templates that exist -- lein new only looks for */lein-template, boot new looks for */boot-template first then */lein-template. clj-new looks for */clj-template, then */boot-template, then */lein-template.


Is that a better explanation @d.ian.b?


(I'm really puzzled by your question, to be honest, because it just "doesn't make sense" so maybe you can explain a bit more about what you mean by the question?)


(I'm wondering if you're confusing the actual templates -- on Clojars -- with the generated project created by * new from the template?)

Danny O’Brien21:08:06

The most sensible one would seem to be (import '[net.sf.biweekly Biweekly]) but that doesn't seem to work. hyellppp


(and in turn boot-new can use templates for lein as well as boot)


The project generated by a template will use whatever build tool the template's author decides.


@danny782 the package name is just biweekly, so try (import '[biweekly Biweekly])

Danny O’Brien21:08:29

it worked! Thank you @jkrasney!