This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-12-04
Channels
- # adventofcode (100)
- # announcements (7)
- # architecture (1)
- # aws (14)
- # beginners (209)
- # calva (30)
- # cider (5)
- # cljdoc (2)
- # cljs-dev (37)
- # cljsrn (2)
- # clojure (133)
- # clojure-dev (20)
- # clojure-finland (1)
- # clojure-italy (10)
- # clojure-nl (19)
- # clojure-spec (56)
- # clojure-uk (49)
- # clojurescript (57)
- # clojurex (8)
- # core-async (2)
- # core-logic (1)
- # cursive (38)
- # data-science (19)
- # datomic (28)
- # devcards (3)
- # duct (8)
- # emacs (28)
- # figwheel (1)
- # figwheel-main (31)
- # fulcro (2)
- # jobs (1)
- # kaocha (1)
- # klipse (2)
- # mount (6)
- # nrepl (43)
- # off-topic (20)
- # pathom (3)
- # pedestal (1)
- # re-frame (15)
- # ring-swagger (1)
- # shadow-cljs (47)
- # spacemacs (19)
- # sql (20)
- # tools-deps (58)
- # unrepl (13)
- # vim (5)
I don't know if I will ever do anything with it, but made a quick copy of that conversation for future reference
I didn't really understand datafy
/`nav` and the underlying protocols until I built the java.jdbc
stuff Friday lunch time and played with REBL, to navigate through data lazily loaded from my test database... 🙂
slightly related to a question i had a couple nights ago:
SET UP: I have a card game like magic where each card does something different. Currently, the cards are implemented as entries in a bunch of hashmaps, name->implementation-map. (The cards are too complicated to be simple edn.) I have the implementations spread over 11 individually namespaced files, based on the card type, for the 1420 cards currently in the game (~16k lines of clojure). I merge the 11 map into a single "card-defs" map and then get
into it with the card name when I need a given effect.
MY QUESTIONS: On the namespace/file side, is this the best way to do it? Should I have them as defs or functions that return the implementation map? Should I split the files into individual card files? How should I Is there some super obvious method for doing this that I've completely missed?
anyone doing AoC?
@nbtheduke I would not recommend putting every card in its own separate file, really. I can't think of any benefit to doing so. If you are using an IDE that lets you find a card definition from its name in some relatively straightforward way, for when you need to do that, then it shouldn't really matter whether it is 1 file or 1420, but I am guessing that 1420 could easily become unwieldy in other ways.
There are maximum limits on the amount of code in one Java method, which turns into limitations on the maximum amount of code in one Clojure function, so my guess is that you would be semi-likely to hit that limit if you tried to put them all in one function.
I have heard that there are limits to the amount of code that can be in the "init" method of a single namespace, and if so, you could potentially hit those limits if you tried to put all of that in a single Clojure namespace.
Yeah, I def don't want or need them in a single namespace or a single file, that sounds like hell
Back when the card pool was much smaller all of the cards were in a single file, but when we hit the limit you mentioned we split it up
@nbtheduke On whether you should have vars or functions ... I wonder if macros would be interesting here with that much code to maintain. So you can alway change your mind later if you want to generate vars vs functions...
Another card game I follow that's written in javascript has all of their cards in separate files, following a more OOP style. I suspect the way we do it is more clojure-y?
Here's an example card from one of the files:
"Explode-a-palooza"
{:flags {:rd-reveal (req true)}
:access {:async true
:effect (effect (show-wait-prompt :runner "Corp to use Explode-a-palooza")
(continue-ability
{:optional {:prompt "Gain 5 [Credits] with Explode-a-palooza ability?"
:yes-ability {:msg "gain 5 [Credits]"
:effect (effect (gain-credits :corp 5)
(clear-wait-prompt :runner))}
:no-ability {:effect (effect (clear-wait-prompt :runner))}}}
card nil))}}
Also, with that many variations I would look at possible abstractions to find a data-oriented DSL to describe a card.
Have any suggestions for data-oriented DSLs?
@nbtheduke Looks already pretty good. But yes, I understand that maintaining that many cards w/ text files could become painful/error-prone. Have you thought about storing those in a database?
By data-oriented DSL I meant designing a data model to be able to represent each card as clojure values. But I know nothing of your domain so I can't help much.
@me1740 Is it possible to save functions in a database?
But back to your original point. I don't see any problem with having 11 namespaces containing a bunch of vars with these maps for each type of card.
Okay, cool
Thanks! I appreciate the help
hey guys, a thought/question: say I would like to create a sequence (and retain its head) which, in order to compute it, I need to perform a side effect (say a network call). Also say, this sequence is quite short and I want to make sure that the computation happens now. Also say, that the computation has quite amount of business logic involved. so my options are:
a. (doall (for
: for
is great for seq comprehension, but the doall
and having all those logic (with side effects) inside the for
macro feels kind of clunky to me.
b. doseq
: great for eager side effects, but bad for creating seqs.
c. reduce
or mapv
are eager and will create a sequence, but will require complicating the computation function even more (because now we need to create a seq and also we do not have the for
magic little helpers :let
:when
:while
)
d. loop-recur
: well, this is the most “low level” and will do the work, but a the messier and least elegant.
so what would you choose? add some missing options if i skipped them.
In your use case what do you think is the best way to separate the side effects from the pure transformations on the data?
Like for example, producing a sequence using your needed side effects (network calls), then using a transducer to forcefully apply pure transformations on the sequence to fit your business logic would in my opinion be much better than putting the side effects and business logic together in a large loop, doseq, etc.
How do I disable Aviso pretty exceptions? Something loads it, and it elides Java methods in its output, but I am trying to debug a Java problem...
Thanks for the hint. I figured out that I can just not depend on Aviso Pretty in Leiningen's project.clj and whatever tries to prettify the exceptions will refrain from doing so, outputting a line that it does this only when I have it explicitly in my project's dependencies.
Different typic: Why do I need the first
in the following code in order to get the actual element at tag
? I would assume xml1->
should already fetch me the first element? (xml/emit-str (first (zip-xml/xml1-> zip tag)))
I think it’s a coll bc there could be multiple tags (like <p> in a <div>)
Funny enough first
gives me the tag
element, including the inner <p>
elements (multiple).
How do I stop clojure.tools.namespace.repl/refresh
from loading my tests? The tests
folder is not included in Leiningen :source-paths
, AFAIK.
They might get loaded through some default profile! > The refresh function will scan all the directories on the classpath for Clojure source files, read their ns declarations, build a graph of their dependencies, and load them in dependency order. (You can change the directories it scans with set-refresh-dirs.) (emphasis mine)
For whatever reason it will start Midje and then everything goes downhill, because the tests modify the environment, e.g. by loading data from different paths than my development setup.
I'm writing something that needs to queue up events then execute an action on them asynchronoously, and wondering what the best way to do this is.
;; I'm polling for an event from point A
;; processing each event
;; Then I want to add the events to a queue, and perform an action on the collected events at certain time intervals.
;; Would there be a problem with using go-loop for something like this? My current solution is something like:
(let [c (chan 1000)]
(go-loop []
(<! (timeout 1000))
(loop [events []]
(if-let [event (poll! c)]
(recur (conj events event))
(do-something-to-events events)))
(recur))
c)
but I think I've heard that there are downsides to running processes like this with go blocks. Does anyone have suggestions for the best way to something like this?does do-something-to-events block?
I think that my current implementation is deadlocking - because I get some of the writes to occur, but eventually they stop and I have to restart my app.
It looks like you're going to loop call do-something-to-events
with an empty vector if the channel is empty.
if you’re doing potentially blockable things then I would put it in a thread, not a go
could you elaborate on that? are go's unsuitable for stuff that may block? (io/network etc)
highly recommended read on this topic: https://martintrojer.github.io/clojure/2013/07/07/coreasync-and-blocking-io
8 by default (but configurable)
I'll try it with a thread. Thanks @alexmiller
@me1740 yeah, my example is a very simplified version of my actual implementation - forgot to include the check for if events
is not empty
yes, that's why I usually say the go thread pool size is whatever concurrency you think you need minus one.
@vale aleph lets you specify an executor to be used by manifold: https://github.com/ztellman/aleph/blob/master/src/aleph/http/server.clj#L446 which gives you control over the thread pooling: https://github.com/ztellman/manifold/blob/master/docs/execution.md
i haven't looked at the aleph default - if it's anything like the manifold default it uses an expanding threadpool and you don't generally have to worry about it. ymmv, but our stuff is largely non-blocking with some very occasional blocking ops and we've never had any trouble with either the aleph or manifold default threadpool configs
only chaining, never @'ing - the one exception being unit tests which get a pass on @'ing
we used to have some usage of @ - but it only ended up causing trouble when we wanted to re-use and compose that code, so we excised it
Hey guys, I'm trying to write a procedure that adds keyvals based on some conditions and have a difficulty with an anonymous function inside ->
macro
(def add-prec #(if (= (:channel %1) "book") (assoc %1 :prec "P0") %1)) ; also works with defn and (fn [] ..)
(defn subscribe-message [symbol channel]
(-> {:event "subscribe" :pair symbol :channel channel}
(add-prec)))
this works perfectly, but I'd like to replace add-prec
with an anonymous function
(defn subscribe-message [symbol channel]
(-> {:event "subscribe" :pair symbol :channel channel}
#(if (= (:channel %1) "book") (assoc %1 :prec "P0") %1)))
And I'm getting CompilerException java.lang.ClassCastException: clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.ISeq, compiling:(core.clj:47:3)
@kyselyradek That ->
isn't doing what you think...
It's a purely syntactic transform and inserts the hash map literal into the first "slot" in that #( ... )
form
I'd use let
for this function as written.
I just realised what the problem was. #()
is just a definition, however ->
macro puts the argument into a call
(defn subscribe-message [symbol channel]
(let [data {:event "subscribe" :pair symbol :channel channel}]
(if (= (:channel data) "book") (assoc data :prec "P0") data)))
The thing is I'm going to have multiple subsequent assocs based on their conditions, that's why I started with ->
in the first place
Ah, OK, so probably as->
is what you want here.
(defn subscribe-message [symbol channel]
(-> {:event "subscribe" :pair symbol :channel channel}
(as-> data (if (= (:channel data) "book") (assoc data :prec "P0") data))))
In this case, yes, cond->
would be great there.
(let [data {:event "subscribe" :pair symbol :channel channel}]
(cond-> data
(= "book" (:channel data)) (assoc :prec "P0")))
(defn subscribe-message [symbol channel]
(-> {:event "subscribe" :pair symbol :channel channel}
(cond-> (= (:channel data) "book") (assoc data :prec "P0"))))
I'm going to look at those, but just out of curiosity, why are those superior to
(defn subscribe-message [symbol channel]
(-> {:event "subscribe" :pair symbol :channel channel}
(#(if (= (:channel %1) "book") (assoc %1 :prec "P0") %1))))
Is it just the readability?(so you can start with ->
and use cond->
or as->
as needed in subsequent branches)
Yeah, that (#(...) )
is horrible IMO 😞
You can tidy it up a little bit if you use the lib thread-first-thread-last-backwards-question-mark-as-arrow-cond-arrow-bang https://github.com/randomcorp/thread-first-thread-last-backwards-question-mark-as-arrow-cond-arrow-bang (sorry 😁)
i'm with enforser here ... it looks like you are reinventing let there radek
Btw am I correct that the let
binding is there just to have something to reference for conditionals?
That means, if my conditionals would actually check only with the function arguments, I could omit the binding and start directly with (cond-> {:event ....} ...
?
I meant this
(defn subscribe-message [symbol channel]
(cond-> {:event "subscribe" :pair symbol :channel channel}
(= channel "book") (assoc :prec "P0")))
Does it look like a proper Clojure code or should I prefer the let
binding (enforser example) even though I will only need that reference on the macro's first line?
if it's a single line I find it hard to read
It depends on the context. If you want to create the hash map before it's passed to the function, you can achieve the same effect with destructring:
(defn subscribe-message [{:keys [symbol channel] :as data}]
(cond-> data
(= channel "book") (assoc :prec "P0")))
but yes, the let binding was just to avoid having a literal map definition repeated multiple times
Okay, another one 😄 If I have let's say 3 transforms per condition, should I prefer repeating the condition
(defn subscribe-message [symbol channel]
(cond-> {:event "subscribe" :pair symbol :channel channel}
(= channel "book") (assoc :prec "P0")
(= channel "book") (assoc :freq "F0")
(= channel "book") (assoc :length "100")
(other-cond) (other-assoc)))
Or nesting with ->
macro?
(defn subscribe-message [symbol channel]
(cond-> {:event "subscribe" :pair symbol :channel channel}
(= channel "book") (-> (assoc :prec "P0")
(assoc :freq "F0")
(assoc :length "100"))
(other-cond) (other-assoc)))
Just use one assoc
with multiple keys
(assoc :prec "P0" :freq "F0" :length "100")
Jinx 🙂
Some folks prefer merge
for that: (merge {:prec "P0" :freq "F0" :length "100"})
but in general, if you want to perform two very different operations based off the same condition then I just generally follow my heart on threading in the cond-> value, or using the same condition. However, if I'm duplicating the condition, then I throw it in a let
so it's only evaluated once.
i.e.
(cond-> {}
true (-> do-one-thing do-another-thing))
OR
(let [is-true? true]
(cond-> {}
is-true? do-one-thing
is-true? do-another-thing))
Yeah, that makes a lot of sense. Another question—what's the best way to learn these best practices?
they probably just come with time, practice, and paying attention to what other people are doing. I've also found reading the examples on both clojuredocs and http://conj.io to be helpful. Every once in a while I'll just look at what exists in clojure.core that I don't know about and try to understand what it's for / could be used for.
can also read through the clojure documentation, which has sections on threading, destructuring, etc. https://clojure.org/guides/threading_macros
not sure there's ultimate best practice. if your co-workers starts to cry whilst code review then you have been bad 🙂 keep it simple. simple is good.
I was lucky, because my first software job was/is in clojure(script), so I had plenty of examples and experienced clojure devs around
@kyselyradek https://www.manning.com/books/clojure-the-essential-reference has a ton of awesome information in it - great to look at examples of specific things from the core library and how they can be used.
It definitely takes a while -- clojure.core
is a pretty huge surface to learn! I've been doing Clojure in production for nearly eight years and I often still trip over a new-to-me core function that makes my code simpler.
re: lesser known core fns: some of us just rediscovered the usefulness of max-key
in solving #adventofcode problems
question ... how does one get the diff between two java.time.Instant instances?
I'd have thought the following would work but I get "No matching method" error.
(.between java.time.Duration inst1 inst2)
is there a vim plugin that just connects to the built-in socket repl like emacs inf-clojure?
or you could try neoterm https://github.com/justinmk/nvim-repl and similar ilk
@lockdown- you could also try #vim
@zugnush: Best explanation so far is http://corfield.org/blog/2018/12/03/datafy-nav/
zippers have the ability to rebuild back to the root, and to replace nodes at a given place with other nodes
@hiredman @shaun-mahood, thanks, some reading to do