Fork me on GitHub
#beginners
<
2019-01-09
>
Gavin Sinclair00:01:41

Hi all, I'm trying to include a third-party dependency ( https://github.com/Engelberg/better-cond ) for the first time. As per instructions, I've included [better-cond "2.0.2"] in my Leiningen dependencies, and (:require [better-cond.core :as b]) in my namespace declaration. I restarted the REPL (in Cursive), hoping the dependency would magically resolve, but I'm left with a syntax error: Could not locate better_cond/core__init.class, better_cond/core.clj or better_cond/core.cljc on classpath. What else do I need to do in this situation? (And what should I read more about to understand this?)

Ben Grabow14:01:46

@gsinclair After adding a dependency to project.clj, tell Cursive to "Refresh Leiningen Projects" in the File menu. That will download any deps you need, and Cursive will know about them after the refresh.

seancorfield00:01:00

@gsinclair What you've done sounds as if it should work. Perhaps ask in #cursive in case this is a Cursive-specific weirdness?

seancorfield00:01:36

I just tried it with clj/`deps.edn` at the command-line and it worked

(! 543)-> clj -Sdeps '{:deps {better-cond {:mvn/version "2.0.2"}}}'
Clojure 1.10.0
user=> (require '[better-cond.core :as b])
nil

Gavin Sinclair00:01:59

I just tried command-line lein repl to see how that would go. Mixed success. When I ran the command, it gave me this positive sign:

Retrieving better-cond/better-cond/2.0.2/better-cond-2.0.2.pom from clojars
Retrieving better-cond/better-cond/2.0.2/better-cond-2.0.2.jar from clojars
But inside the REPL itself:
aoc.core=> (require '[better-cond.core as b])

Execution error (FileNotFoundException) at aoc.core/eval1526 (form-init5794080410004864811.clj:1).
Could not locate better_cond/core/as__init.class, better_cond/core/as.clj or better_cond/core/as.cljc on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.
😞

noisesmith01:01:00

it thinks you want a namespace with as as a component

Gavin Sinclair01:01:53

Oh my goodness, how silly. Thanks @noisesmith.

Gavin Sinclair01:01:35

Works now. I'll follow up with #cursive.

noisesmith01:01:50

there's a rarely used feature, you can replace (require 'foo.baz) with (require '[foo baz]) and due to as being a symbol and not keyword clojure was trying to use that syntax

sova-soars-the-sora03:01:02

Could not transfer artifact org.apache.ant:ant:pom:1.8.2 from/to central (): Received fatal alert: protocol_version
o_O

nadejde06:01:17

hello! I have a really beginner level question regarding protocols and implementing methods. I'm just learning the basics and I hit a problem I don't understand. I'm creating a basic protocol and then using defrecord to implement its method. I have this function that creates instances of the Printer class that implements TestProtcol:

(defprotocol TestProtocol
  (test-method [this text]))

(defrecord Printer [prefix]
  TestProtocol
  (test-method [this text]
    (println prefix text)))

(defn hello-printer []
  (->Printer "hello"))

(test-method hello-printer "me")

nadejde06:01:10

When I try to call test-method I get this error in the repl:

IllegalArgumentException No implementation of method: :test-method of protocol: #'hangman.core/TestProtocol found for class: hangman.core$hello_printer  clojure.core/-cache-protocol-fn (core_deftype.clj:568)

nadejde06:01:20

Can anyone please point me in the right direction to understand what I'm doing wrong?

nadejde06:01:52

Thank you!

lispyclouds06:01:24

The

(defn hello-printer []
  (->Printer "hello"))
should be
(def hello-printer
  (->Printer "hello"))
i think

👍 5
nadejde06:01:00

yeah so I was playing with this also and this seems to work:

(test-method (->Printer "hello") "me")

dpsutton06:01:16

do you understand why?

nadejde06:01:44

No. I would have expected the function called hello-printer would return an instance of that class. and then the test-method call would happen on that instance of the class

dpsutton06:01:01

you didn't call it though.

nadejde06:01:15

I'm missing a set of paranthesis!!

nadejde06:01:21

thank you!

dpsutton06:01:25

of course 🙂

nadejde06:01:10

Cheers for the help:)

👍 10
JanisOlex08:01:03

hello, I have question about !swap If I have this line (swap! my_atom assoc (:key id) (function-producting-state x y)) In case if swap! decides to run the assoc again (because somebody did update atom, so it wants to do it on the new value) will my function-producing-state will be also executed twice, or the value is "locked" on the first attempt, and it is just applied to the new atom value, in case if it changed?

JanisOlex08:01:43

also follow-up question, how actually test such case in unittest...

JanisOlex08:01:33

oops I mean (:key bigger_structure) instead of (:key id)

leonoel09:01:39

swap! is a regular function, its arguments are evaluated eagerly once, so function-producting-state is guaranteed to run exactly once. assoc may be re-run multiple times, and this is safe because it's a pure function.

pierodactylus10:01:55

Hi everyone! I’m just starting with Clojure and already have several problems with Leiningen, for which I couldn’t find any reference online. I’m running Leiningen 2.8.3 on Java 10.0.2 (OpenJDK), installed with Homebrew on MacOS Mojave. When running lein repl I get the REPL starting, but backspaces advance the cursor to le right, rather than deleting the previous character. This is the first problem.

pierodactylus10:01:14

The second problem arises when trying to use the venantius/ultra plugin. I changed my .lein/profiles.clj to look like this:

{:user {
        :plugins [[venantius/ultra "0.5.2"]]
}}
and when I run lein repl get this error:
Warning: implicit middleware found: ultra.plugin/middleware
Please declare all middleware in :middleware as implicit loading is deprecated.
[WARNING] No nREPL middleware descriptor in metadata of #'clojure.tools.nrepl.middleware.render-values/render-values, see nrepl.middleware/set-descriptor!
nREPL server started on port 58327 on host 127.0.0.1 - 
ERROR: Unhandled REPL handler exception processing message {:id 9268a5a3-6560-4ba3-89fe-62d372d5aef7, :op clone}
java.lang.IllegalArgumentException: No implementation of method: :send of protocol: #'nrepl.transport/Transport found for class: clojure.tools.nrepl.middleware.render_values$wrap_renderer$reify__3621
	at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:568)
	at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:560)
	at nrepl.transport$fn__5123$G__5118__5130.invoke(transport.clj:16)
	at nrepl.middleware.session$register_session.invokeStatic(session.clj:149)
	at nrepl.middleware.session$register_session.invoke(session.clj:142)
	at nrepl.middleware.session$session$fn__5712.invoke(session.clj:193)
	at nrepl.middleware$wrap_conj_descriptor$fn__5347.invoke(middleware.clj:17)
	at clojure.tools.nrepl.middleware.render_values$render_values$fn__3627.invoke(render_values.clj:42)
	at clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__923.invoke(middleware.clj:22)
	at nrepl.server$handle_STAR_.invokeStatic(server.clj:18)
	at nrepl.server$handle_STAR_.invoke(server.clj:15)
	at nrepl.server$handle$fn__5743.invoke(server.clj:27)
	at clojure.core$binding_conveyor_fn$fn__4676.invoke(core.clj:1938)
	at clojure.lang.AFn.call(AFn.java:18)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:844)
at which point the REPL hangs and I have to kill it from another terminal.

pierodactylus10:01:34

Any help with any of these issues would be much appreciated, thank you!

pierodactylus10:01:01

(apparently, no one is complaining about those things online… but I can’t figure out what could be wrong in my configuration)

lispyclouds10:01:06

@piero.cornice Im not sure about the first one, but the second issue is caused by oudated code in the ultra plugin which doesnt work if youre using lein 2.8.2+. See this comment from me: https://github.com/technomancy/leiningen/issues/2497#issuecomment-446631724 and the following thread for more info. Only solution is to remove it or use lein 2.8.1 or wait for a new release.

pierodactylus10:01:10

thank you @rahul080327, I’ll try with lein 2.8.1

pierodactylus11:01:58

Update: Apparently the first problem (backspace not working properly) went away by re-installing Leiningen:

brew uninstall -f leiningen
brew install leiningen
I’m sure I’ve tried that yesterday with no success, but somehow it worked today.

victorb12:01:00

probably something related to rlwrap since you're on macOS it seems

JanisOlex13:01:50

can someone explain this

manutter5113:01:03

I’m a little bit out of my depth here, but it looks to me like def does not evaluate its first argument, it just takes literally what’s there. Kind of like a macro, except def is a special form, not a macro. So the error message you’re seeing is saying “`(symbol "my-id")` is not, itself, a symbol.” It’s a form that evaluates to a symbol, but as far as def is concerned, that’s not the same thing.

JanisOlex13:01:43

@manutter51 Ahh makes sens. THen how could I implement functionality so that if I have user-defined ID, then I can create atom symbol with that ID, and whenver there is state change, then by ID I can perform atomic swap? So I would have something like (swap! user-defined-id (new-state-of-system x y z))

lispyclouds13:01:13

@olekss.janis maybe use a map? {id1 (atom) id2 (atom)}

JanisOlex13:01:27

then the structure of the map should itself be atom

manutter5113:01:02

I was going to say something similar: (atom {"id1" "value" "id2" "value"})

✔️ 5
manutter5113:01:43

I think it’s better to have one atom with multiple keys than to have a map with multiple atoms

JanisOlex13:01:48

but then you can't add dynamically new atoms to the map, as it is immutaale

JanisOlex13:01:40

But if you have atom with multiple keys, then swap function will be assoc function, but I want my actual transformation to be the update function, so that whenver there is value collision, my function is executed again with new - current atom value

JanisOlex13:01:59

so my state-producing function IS the update function of the atom

JanisOlex13:01:45

or might be I am stoopid and don't understand what I want - also could be a case

lispyclouds13:01:17

can you give an example of the data you want to store? and the transformed state?

Henry13:01:40

you could put the map in an atom if you really need that

Henry13:01:34

oh you've already been there, I'm bad at reading

JanisOlex13:01:38

I want to store big map of game-state, which contains players, board etc.... So I want that many players can define their game session and play, and each set of such players would have one atom to update, but the update would be transition from one game state into anoter applied player action THe trick here is that because players might make their turns and "submit" them concurrently, I want the atom to take care of 'retry' of game state update because of this, so if P1 and P2 both submit their version of tame, which is transformation from original 1 state (in the atom), then they both will see the different version as they don't see each others version. One of players will succeed, but another one will not, because atom will notice that it is already changed... and I want so that atom automatically applies P2 transformation on the result produced by P1 in retry attempt

manutter5113:01:56

It should work correctly. Let’s say you have an atom containing the map {"id-1" "val-1" "id-2" "val-2"}. Two threads each try to update the map at the same time, one setting id-1’s value to “val-1a”, the other setting id-2’s value to “val-2a”. As long as each one calls (swap! my-map assoc "id-1" "val-1a") or (swap! my-map assoc "id-2" "val-2a") you’ll end up with your atom containing {"id-1" "val-1a" "id-2" "val-2a"}. Re-trying with the new value is what atoms are built for.

JanisOlex13:01:07

and I am ok that P2 might get exception, and resubmits explicitly, in case if his version breakes game rules after P1 transformation...

awb9913:01:38

I try to reduce a sequence this way. The keywords basically are names of performance-counters and the values is the # of times. I think this should be an easy task, but I cannot find the right reduce function. Any ideas?

lispyclouds13:01:29

@UCSJVFV35 (merge-with + {:a 1 :b 1} {:a 1} {:b 2} {:c 5}) ? Or if its a list: (apply merge-with + [{:a 1 :b 1} {:a 1} {:b 2} {:c 5}])

awb9913:01:58

Thank you!!!

awb9913:01:06

I knew there is a function that makes this easy

awb9913:01:31

many thanks!

lispyclouds13:01:39

yeah clojure generally has some tricks up its sleeve 😉

JanisOlex13:01:32

@manutter51 exactly, but now I want those atoms to be defined by game ids... which might come and go... so to have separate atom to each game session, so that on the server there can be many games...

JanisOlex13:01:55

but it would be stupid to update WHOLE array of games, if I know that particular set of players touch only one game

JanisOlex13:01:06

so naturally - one atom per game state, which is then shared and changed by players

manutter5113:01:11

Ok, you can do that with nested maps — it sounds more complicated than it is.

JanisOlex13:01:22

can't I do it with some sort of macro

JanisOlex13:01:34

which obfuscates my def <dynamic-id>?

manutter5113:01:05

A macro would probably be overkill, you can just use assoc-in or update-in for nested maps.

JanisOlex13:01:06

because after I have my symbol, I can use (symbol game-session) to access the atom

manutter5113:01:06

It’s easier to use (assoc-in games-atom [game-session player-id] :new-value)

JanisOlex13:01:47

but assoc-in won't work, then it will be too much false-positives... it means, player will re-apply the transformation function to the legit state, just because somewhere else in the map there is update of the game, not related to THIS players game

JanisOlex13:01:57

scope is too broad

JanisOlex13:01:55

@rahul080327 can't merge, as, for example, game rule states, that there can't be more than 5 figures on one game room, so merge might break it "outside" legit transformation function which enforces game rules

lispyclouds13:01:37

ah that answer was for another person 😅

manutter5113:01:21

I may not be following your train of thought clearly, but assoc-in should work just fine for updating separate parts of the map separately. If you have :game-1 and :game-2, and each game has :player-1 through :player5, you can safely apply (swap! all-games assoc-in [which-game which-player] new-value), and the end result will be the same as if each swap! happened sequentially instead of concurrently.

manutter5113:01:19

Give me a few minutes, I’ll try and come up with a test program to force a collision and see what happens.

manutter5114:01:13

If I did that right, I created 2 threads. One tries to do an update that takes 5 seconds to set :sub-1 :a to 10, the other runs an update that takes 2.5 seconds to set :sub-2 :b to 20. The quicker thread completes first, causing the first thread to re-try, but it re-tries with the updated map as updated by the quick thread. The net effect is as if both threads ran sequentially, which should be safe enough for your game management.

Alex Miller (Clojure team)14:01:13

if you need greater concurrency, make a map of atoms

Alex Miller (Clojure team)14:01:41

unless you need consistency across parts, then you get into stm territory

JanisOlex14:01:52

@manutter51 Ok. Now I see the magic... 🙂 Thanks

arnoha16:01:56

hi, seeing a strange issue where a function works fine when it’s run in the REPL but when I load it in from a file via load-file it’s not getting run. for example, at the repl this works fine:

user=> (for [[k v] {:one 1 :two 2 :three 3}] (println k " -> " v))
(:one  ->  1
:two  ->  2
nil :three  ->  3
nil nil)
but throw this into a file arnoha.clj:
(ns arnoha)

(defn foobar []
	(do
		(println "foo")
		(for [[k v] {:one 1 :two 2 :three 3}] (println k " -> " v))
		(println "bar")))

and then run it
user=> (load-file "arnoha.clj")
#'arnoha/foobar
user=> (ns arnoha)
nil
arnoha=> (foobar)
foo
bar
nil
arnoha=> 
where am I going wrong here?

Alex Miller (Clojure team)16:01:34

the repl is forcing it’s realization to print it, but nothing is doing that in the function

Alex Miller (Clojure team)16:01:52

you could swap for with doseq here

Alex Miller (Clojure team)16:01:52

many of the “do” functions work with side-effecting things (like println)

arnoha16:01:03

thanks, will try it

arnoha16:01:25

curious, how does the REPL force the for to be evaled?

andy.fingerhut16:01:58

The "P" is "print", and printing a lazy sequence causes it to be evaluated.

andy.fingerhut16:01:22

or at least as much of it as the printing function is configured to print -- there are some print functions that can be configured to stop printing after N elements, and then they would also stop evaluating at that many elements, or perhaps a few more because of "chunking" optimizations used by some lazy sequences.

macrobartfast16:01:23

why doesn't this return a hash-map of values?

(let [foo (atom {})]
  (for [x (range 4)]
    (swap! foo conj {(keyword (str "value-" x)) x}))
  (str @foo))

macrobartfast16:01:33

I have had the hardest time trial-and-erroring getting lazy values back out...

macrobartfast16:01:45

it's my main stumbling block at this point.

andy.fingerhut16:01:11

Lazy sequences and side effect-y things like swap! often do not mix well.

andy.fingerhut16:01:26

Avoiding side effect-y kinds of things in lazy code is one recommendation. Another is that if you want side effect-y things, force the evaluation of the lazy things with something like doall, or avoid the use of lazy constructs, e.g. in this case replace for with doseq

macrobartfast16:01:05

this is a simpler version of my effort to generate a dummy hash-map... I'll play with doseq.

macrobartfast16:01:06

ok. that worked. thx!

andy.fingerhut16:01:48

Here is another snippet of code that produces the same map, as an alternative that avoids the use of atoms/swap!/etc.: (into {} (for [x (range 4)] [(keyword (str "value-" x)) x]))

arnoha16:01:53

thanks @andy.fingerhut, was also under the impression that a (do ..) block would force evaluation of everything, lesson learned

andy.fingerhut16:01:58

If one or more of the expressions inside (do ...) returns a lazy sequence, they will not be forced to be evaluated simply because they are inside of the (do ...).

andy.fingerhut16:01:22

If a lazy sequence is returned by the (do ...) because it is returned from the last subexpression, something outside of the do might force it to be evaluated later. If a lazy sequence is returned by a subexpression of (do ...) that is not the last subexpression, nothing will force it to evaluate, ever.

macrobartfast16:01:23

@andy.fingerhut thank you... the 'into' version is way better!

Daniel Hines17:01:37

Total noob at deps.edn. Given the following alias section:

:aliases
 {:dev
  {:extra-deps
   {com.datomic/ion-dev {:mvn/version "0.9.176"}}}

  :visualizer
  {:extra-deps
   {hodur/visualizer-schema  {:mvn/version "0.1.1"}
    com.datomic/client-cloud {:mvn/version "0.8.66"
                              :exclusions [org.eclipse.jetty/jetty-http
                                           org.eclipse.jetty/jetty-util
                                           org.eclipse.jetty/jetty-io]}}
   :main-opts ["-m" "hodur-visualizer-schema.main"
               "hodur-example-app.visualizer"]}
…
How do I run it with the visualizer settings?

Alex Miller (Clojure team)17:01:28

while that will work, -A:visualizer is preferred (the alias flags take a concatenated set of alias keywords like -A:a:b:c)

🥦 5
Daniel Hines17:01:42

Now that I'm looking at that, the ability to express extra deps like that is really nice. Seems like a no brainer, but it's sorely lacking from npm. Good work Core team!

Dave18:01:09

Hello everyone, I am trying to learn ClojureScript, and I am having trouble understanding the -> operator. I have looked up the Arrow (Computer Science) and looked up Monads, and I still don't understand what these functions are doing. Does anyone have a good reference?

hiredman18:01:56

-> is not related to those things at all

hiredman18:01:37

-> is a simple syntax transformation (-> a f) transforms to (f a)

hiredman18:01:02

(-> a (f 1 2)) transforms to (f a 1 2)

hiredman18:01:17

an the transformation is recursive

hiredman18:01:59

(-> a (f 1 2) (g 3 4)) becomes (-> (f a 1 2) (g 3 4)) which becomes (g (f a 1 2) 3 4)

Lennart Buit18:01:10

its called “thread-first”

Lennart Buit18:01:20

If you want to google about it ^^

noisesmith18:01:06

you'll find erroneous claims that -> is thrush - it isn't because thrush is a function combinator and -> is a syntax transform

noisesmith18:01:55

eg

user=> (->> (+ a b) (let [a 12 b 30]))
42

Dave18:01:33

Awesome, thanks for the leads everyone!

Dave18:01:46

No wonder I was confused.

manutter5118:01:22

It's always fun to try googling for punctuation

Lennart Buit18:01:04

thats why haskell has hoogle

danielneal10:01:44

yeah when they added the [:> component] to reagent I had a really hard time googling what [:> meant

jaide19:01:43

Does com.billpiel.sayid.nrepl-middleware only work in the context of a project?

jaide20:01:11

What version of Clojure does lein-repl use outside of a project?

jaide20:01:59

Ok the error that I was running into when running lein repl outside of a project is because of the middleware depending on clojure 1.9+ but I think lein repl outside of a project may be using Clojure 1.8 or below.

jaide20:01:40

However, I don’t really need to use lein repl outside of a project and when I do there’s always clj so I’m not going to worry about this further.

jaide20:01:17

What is the best way to concat two vectors together (vec (concat vec1 vec2)), (into [] (concat vec1 vec2)), or (reduce conj vec1 vec2)?

dpsutton20:01:31

(into vec1 vec2)

jaide20:01:04

Whoa that is slick. Thanks once again.

pberganza20:01:39

Just to note that into can work with pretty much any collection (lists, vectors, hash-maps)

👍 5
jaide20:01:08

Different types may result in different ordering though right?

jaide20:01:20

Err nvm, I’ll just try it 🙂

pberganza20:01:47

Ah. Different types would cast (idk if that's the term) one collection into the other. "into" is also used to turn one collection into another. For example: (into '() [1 2 3]) would return (1 2 3)

jaide20:01:39

Just played with it (into '(1 2 3) '(4 5 6)) => (6 5 4 1 2 3)

jaide20:01:07

It’s not a problem for me, just wanted to try and connect some dots from other discussions I’ve seen around here.

Alex Miller (Clojure team)20:01:11

of note is that the time is O(n) on size of the second collection (whereas concat is O(1))

👍 5
Alex Miller (Clojure team)20:01:58

there are some alternate data structures outside clojure core that are designed for constant time concat of vector like data (indexed) if that’s what you really need

jaide20:01:27

Good to know. I’m just joining a computationally small list of filename and template pairs in a lein template for organizational purposes so not too concerned about performance.

jaide21:01:03

Actually I should back up. Perhaps there’s a better way to architect this problem. Say I’ve got a list of common files, and based on conditionals, append different vectors of files to it. How would you architect it? My instinct is to

(defn common-files
  []
  [".gitignore"
   "README.md"
   "project.clj"]

(defn fig-files
  [files]
  (into files
        ["dev/user.clj" "figwheel-main.edn"]))
    
(defn shadow-files
  [files]
  (into files
        ["package.json" "shadow-cljs.edn"))
       
       
(defn request-files
  [opts]
  (cond-> (common-files)
          (some #{"+fig"} opts) (fig-files)
          (some #{"+shadow"} opts) (shadow-files))

jaide21:01:46

Changed sets of files -> vectors of files. That term was misused.

Lennart Buit21:01:15

sets and union?

Lennart Buit21:01:43

or is order important ^^?

jaide21:01:15

Not for functionality but would be appreciated for debugging\readability.

noisesmith21:01:32

you could replace into with something that combines the two vectors and uses distinct

noisesmith21:01:05

into can use a (distinct) transducer, but that doesn't take the first collection into account

jaide21:01:09

I think for the nature of this problem I’m not worried about duplicates, values will be known ahead of time.

noisesmith21:01:32

without threading (into (common-files) (concat (when (some ...) fig-file-vec) (when (some ...) shadow-file-vec)))

noisesmith21:01:59

where those "vec" values are just the file list vector, without the merging logic

Lennart Buit21:01:44

yeah, that shadow-files and fig-files append feel off to me. As if it is some form of builder-like interface

noisesmith21:01:09

precisely, hiding data in clojure is usually a sign you are doing it wrong

noisesmith21:01:48

a possibility: (def additions {"+fig" ["dev/user.clj" "figwheel-main.edn"] "+shadow" ["package.json" "shadow-cljs.edn"]}) (into common-files (vals (select-keys additions opts)))

noisesmith21:01:10

you can literally look up the files to add based on the opts as map keys

jaide21:01:46

(reduce into common-files-vec (keep some? [(when (some #{"+fig"} opts) fig-files-vec)
                                           (when (some #{"+shadow"} opts) shadow-files-vec]))

jaide21:01:57

But I think your map idea just kicked my ass

noisesmith21:01:50

I think that keep some? can be replaced with concat - concat has the right behavior (no-op for nil, add to coll for coll input)

noisesmith21:01:05

but yeah, the map thing removes conditionals altogether

hiredman21:01:54

(require '[clojure.set :as set])

(def files
  (set/index
   #{{:filename ".gitignore" :collection :common-files}
     {:filename "README.md" :collection :common-files}
     {:filename "project.clj" :collection :common-files}
     {:filename "dev/user.clj" :collection :fig-files}
     {:filename "figwheel-main.edn" :collection :fig-files}
     {:filename "package.json" :collection :shadow-files}
     {:filename "shadow-cljs.edn" :collection :shadow-files}}
   [:collection]))

(defn request-files
  [opts]
  (for [collection (cond-> [:common-files]
                     (some #{"+fig"} opts) (conj :fig-files)
                     (some #{"+shadow"} opts) (conj :shadow-files))
        {:keys [filename]} (get files {:collection collection})]
    filename))

jaide21:01:22

Didn’t even know about clojure.set until now.

Lennart Buit21:01:16

ohh, so many problems can be solved with proper set math

jaide21:01:41

Thanks for the help. It’s sometimes tough to stop yourself and ask for advice when you have a quick, working solution in mind to a seemingly small problem but you can learn so much so fast!

Lennart Buit21:01:33

haha I sometimes feel guilty for asking too much

jaide21:01:11

That’s true, but I feel less guilty when it’s a smaller generalized problem than issues like “Why does framework x.y.z throw InvalidArgumentException when used with this non-standard nREPL middleware?” I suppose you have to find the balance, fortunately this channel is a bit slow atm.

eval-on-point21:01:23

Hi, all. I am nearing the end of the example problems on 4clojure, and am wondering if there are any other resources for practicing that might help me get my head around any of the more advanced features like async, macros, transducers, etc.

Lennart Buit21:01:20

clojure for the brave and true ofcourse, I still have clojure applied on my desk, but reading books is not my forte

eval-on-point21:01:46

That's a good idea, thanks

jaide21:01:36

https://github.com/eccentric-j/book-report is coincidentally a library I made, using macros, to help make learning from books easier 🙂

Lennart Buit21:01:15

CftBaT is one of those great books, if you have read Learn You A Haskell, you will like it very much

eval-on-point21:01:16

I've read through the book, but never did the practice problems, so that makes probably the most sense

jaide21:01:19

Clojure for the Brave and True does have good problems for those topics. I never did quite solve the macro exercise for creating an order-of-operations compliant infix macro but I got the machinery working and moved on. Another option is http://clojurekoans.com/.

Lennart Buit21:01:43

I must admit, I don’t write macros that often. They don’t play too well with tooling such as Cursive and I am majorly allergic for IDE errors

eval-on-point21:01:01

my perception as a beginner is that they are only really going to be written as the kind of major workhorse of a larger library

Lennart Buit21:01:42

many of the stuff you use is macros, if/`and`/`or`/…

eval-on-point21:01:48

Right. I've been using evaluation properties of the logic macros to shorten my solutions on 4clojure actually

eval-on-point21:01:16

So I guess other than just syntactic sugar, they are useful for when you need to control evaluation?

eval-on-point21:01:36

but it feels like I already can naturally do that with the provided core library pretty well

Lennart Buit21:01:57

yes! I was about to type that! Or when you are a library author that wants to have some custom syntax.

bowie 5
eval-on-point21:01:19

thanks all. I'll check out the koans website once I finish up the 4clojure problems. then will probably do the exercises in BaT, and then seek to grab another book that covers whatever I should learn next

👌 10
jaide23:01:41

(apply str (drop 1 "hello world")) Is there a better way to achieve that?

dpsutton23:01:49

(subs "string" 1)

dpsutton23:01:16

beware of nil though

jaide23:01:18

Ah thanks. I was looking for something that in clojure.string. So if it receives nil it blows up?

jaide23:01:35

Tried it. Yes it does.

seancorfield23:01:45

(subs (str x) 1) ? Maybe that will throw index out of bounds?

jaide23:01:15

Throws index out of bounds, but for the use case it should be fine. Thanks

noisesmith23:01:57

((fnil subs "") x 1) is another option

👍 5
5
noisesmith23:01:23

making a wrapper for subs that handles index out of range and nil without errors is a fun exercise too