Fork me on GitHub

I forgot who recommended claypoole to me, but that is a nice library if you easily want to do a bunch of stuff in parallel without fancy apis


What is the proper way to loop over a lazy collection to “do” something .. i need to loop over a list and swap! an atom.


Is it doseq ?


doseq if you need bindings and an arbitrary body, run! if you have a function that should be called for each element in order


Would y’all have advice for choosing libraries for building a web app with clojure? I am facing an issue where I am searching for a solution to some problem in a web server, and find a site pointing to a library, but the library hasn’t had any updates for 10 years. It’s hard to tell if the library is extremely stable (and with clojure, this seems likely) or if it’s outdated.


I am also facing an issue with limited documentation. I am currently working to use compojure, which I think just has a slim wiki for documentation, which leads me to searching the web for how to do something with compojure, but much of the results are also 8-10 years old.


I don’t mean this as a complaint, it’s beautiful open source software and I know it’s people behind it all-- more just seeing if there’s some good rules of thumb experienced folks use when picking libraries or building up their stack.


@U011DMQ8DSS Which library is that?


For almost all our web apps, we use Ring, the "standard" Jetty adapter, Compojure for routing, and then Component for managing start/stop and dependencies. We have one app using Bidi for routing, and one app using Netty directly via Java interop (because it has to support on the server).


It's certainly true that a lot of Clojure libraries have pretty minimal documentation. Clojure was originally aimed at experienced developers so there's a lot assumed in most documentation and often very little in the way of examples or any sort of "cookbook". As more developers -- and less-experienced developers -- have come to Clojure, some maintainers and projects are making more of an effort to provide more comprehensive documentation but it's all a bit hit or miss...


I was following advice (that might’ve been from you!) that I read on clojureverse, to start as simply as possible with compojure and ring and I quite like them, and how straightforward everything feels.


Yup, very likely my recommendation.


What is the library that is ten years old?


My issue came when I was trying to persist some session data using the ring-defaults and some middleware, and it worked kinda…and when I read some docs i was pointed to, which was 10 years old.


Similar libraries that pop up during my web crawling is deprecated lib-noir, or the 5 yo , and it made me realize I hadn’t developed a good sense on when a library was basically done and stable, and when it was out-of-date.


Never heard of sandbar. I try to avoid session data in general tho' but using cookie storage should be reasonable for basic usage (and anything that can't be done that way probably shouldn't be using session data IMO).


ah, good to know!


I saw you had a usermanager repo for learning, i should take a look at that cos it has basically the same stack i’m using (though i’d never heard of component before today!)


Buddy is the other commonly-referenced auth library for Clojure and it's slightly more up-to-date than Friend but I don't know much about either. We had a requirement for OAuth2 and needed to write our own Identity Server for... reasons... So we have an Auth Server, a Login Server, and our apps all separate.


In theory, anyone could request a client ID/secret from us (registering an app with us) and then use standard OAuth2 client libraries against our system 🙂 One day, maybe we'll test that theory...

😁 3
Clark Urzo02:12:10

So I'm using deps.edn with shadow-cljs but I can't seem to get a useful REPL out of it

Clark Urzo02:12:17

Here is my deps.edn file

{:paths   ["src/clj" "test/clj" "src/cljs" "test/cljs"]
 :deps    {org.clojure/clojure {:mvn/version "1.10.1"}}
 :aliases {:shadow-cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.11.8"}
                                      binaryage/devtools {:mvn/version "1.0.2"}
                                      proto-repl {:mvn/version "0.3.1"}
                                      reagent {:mvn/version "0.8.1"}}
                         :main-opts  ["-m" "shadow.cljs.devtools.cli"]}}}

Clark Urzo02:12:54

And my shadow-cljs.edn file

{:deps {:aliases [:shadow-cljs]}

 :nrepl        {:port 3333}
 :builds       {:app {:target :browser
                      :output-dir "public/js"
                      :asset-path "/js"

                      :modules {:main {:entries [app.core]}}
                      :devtools {:http-root "public"
                                 :http-port 3000}}}}


For looping over a collection laziness doesn’t really implicate it. The real question is if you need the result from doing whatever or just run a function for each thing in the collection


Clark what’s your editor? Or just a repl at the command line?

Clark Urzo02:12:36

command line

Clark Urzo02:12:17

there's a note in the shadow-cljs docs that says aliases aren't applied when connecting to a running server but i don't know how to fix that


What are you trying? There’s extensive documentation. (Oh I see you’ve seen these)

Clark Urzo02:12:20

I want to have a cljs repl that's connected to the browser


Another thing you could try is the repl api. Just start the clojure repl with your alias and then use the dev tools api for shadow to start your build

Clark Urzo02:12:10

Mmm, not sure how to do that. I'm just doing clj -A:shadow-cljs watch app

Clark Urzo02:12:47

which I'm assuming starts a regular clojure repl instead of a cljs one


do you get a repl prompt when running that?

Clark Urzo02:12:43

I feel like I'm doing several things wrong here. For instance, I'm still using lein repl :connect localhost:3333 to connect to the nREPL instance

Clark Urzo03:12:10

@dpsutton not really, but it does say it's started an nREPL server on the port I gave it


ok. that's a good start. there's probably a webserver running. do you see any information about that?

Clark Urzo03:12:14

This is the output

> clj -A:shadow-cljs watch app
shadow-cljs - HTTP server available at 
shadow-cljs - server version: 2.11.8 running at 
shadow-cljs - nREPL server started on port 3333
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build completed. (175 files, 0 compiled, 0 warnings, 3.36s)

Clark Urzo03:12:33

which is the typical shadow-cljs output


cool. open up http://localhost:3000 in a browser

Clark Urzo03:12:09

Yup, it works. The page renders. It's just that, if I go to the dev console even if there's shadow-cljs: ready! it doesn't seem to recognise (js/alert)

Clark Urzo03:12:19

So i'm assuming it means the repl wasn't set up properly

Clark Urzo03:12:45

Then when I connect to the nREPL port via lein repl :connect localhost:3333 I'm plopped into a Clojure REPL, not CLJS, so I'm assuming I'm not connected to the right server

Clark Urzo03:12:23

If I do (js/alert with the build window open it should work, right? An alert should pop up?

Clark Urzo03:12:36

Oh wait, so the browser console isn't connected to the nREPL instance after all. It's just a regular browser console.

Clark Urzo03:12:10

That's why (js/alert) wasn't working. (also, do you want to do this in a thread?)

Clark Urzo03:12:16

(thanks btw)


of course. you're welcome. what's left to do?


i imagine in your terminal you now have a cljs repl

Clark Urzo03:12:18

Not yet. It's still purely a Clojure REPL.

Clark Urzo03:12:36

I'm not sure how this all works out under the hood. Is it because I'm using clj to start the nREPL server?


do you have a prompt where you can evaluate things?

Clark Urzo03:12:41

If I connect to it via lein repl, yes

Clark Urzo03:12:16

but otherwise, calling my clj command doesn't give me a REPL


ok i misunderstood. i thought there was a repl


run shadow-cljs cljs-repl app from another terminal

🎯 3

which will connect a client to the running watch

Clark Urzo03:12:43

ohhh wow I can't believe it was that simple

Clark Urzo03:12:09

so to connect to a running cljs repl you need to use shadow-cljs instead of clj , got it

Clark Urzo03:12:30

It works, I can send commands to the browser now!


awesome. yes shadow runs a server process that can also watch the build files and keep recompiling. then you connect to this and you're good to go

clj 3
Clark Urzo03:12:06

Thanks @dpsutton it works now!


Is there a state of the art way of accomplishing a Clojure notebook, a la Jupyter? I’m going to demo some things, mainly usage of rest APIs, and would like something like that. Pointers?


those two are great and I used them both in different context during presenting clojure to colleagues

Michaël Salihi10:12:22

Yes, Gorilla and Maria are great! Otherwise, I recently found this library too. It can be useful if you are already comfortable with Jupyter. 👍


I've found clojupyter to work great. Easy to set up (on linux at least).


Fantastic, thanks! Obviously the net is full of stuff, but having a recommendation (not from an algorithm...) makes all the difference. 😄👍

👌 3
practicalli-john13:12:51 is a relatively new notebook for Clojure and is very simple to use and can also incorporate vega graphics (which is fast becoming the defacto approach) If you want a multi-language (written in Clojure but supports lots of other language journals), try Its an amazing project and seems very powerful. You may be interested in the community, lots of discussions on a wide range of data science related topics.

Lisbeth Ammitzbøll Hansen11:12:27

I am trying to call create-user multiple times - based on a map input - like this: (and then calling (get) to extract :id from the map of users just generated) test-user-ids (map #(get % :id :customer_id) (map #(create-user %) (gen-bodies 2 "user" customer-id)) ) But create-user function does not actually get called (and hence users not created) before I do this: _ (println "test-user-ids : " test-user-ids) I have also tried with (fn) instead of #(), but with the same result : test-user-ids (map (fn [userbody] (get (create-user-with-time-travel userbody) :id)) (gen-bodies 2 "user" customer-id) ) I would be very happy if you could explain whats happening here?


map is lazy, and not designed for side effects but for creating values, you can replace map with run! if you don't need the return values, or mapv if you do. Also, (map f (map g l)) can be replaced with (map (comp f g) l)


(or (run! (comp f g) l) , (mapv (com f g) l) etc.)


also, for all f, where f is a function and not a method, #(f %) can be replaced with f - in your case #(create-user %) can be replaced with create-user

Lisbeth Ammitzbøll Hansen08:12:41

Thanks a lot for your answer - it was a great help :thumbsup:


Hey, I am trying to copy data(`io/copy`) from ZipInputStream more particularly from ZipEntry to BufferedWriter. I am getting something like so: No method in multimethod 'do-copy' for dispatch value: [ .BufferedWriter]


I think I am hitting a problem with multimethod dispatch and it has to be exact (inheritance ignored).


A zipentry is not an inputsream


If I recall that is not the correct way to use a zipentry, so you may want to check out the docs and maybe find some examples


yes you are right, i should use zipstream for io/copy and .getNextEntry to move the cursor between

Ben Sless15:12:22

Is there a way to override the JAVA_CMD for clj?


For what OS and/or shell?


I’m a bit rusty in bash, but I think you should be able to do JAVA_CMD=/some/other/java clj ...

Ben Sless15:12:32

Not sure this will work is java executable is found before

JAVA_CMD=$(type -p java)
set -e
if [[ ! -n "$JAVA_CMD" ]]; then
  if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
    >&2 echo "Couldn't find 'java'. Please set JAVA_HOME."
    exit 1

Alex Miller (Clojure team)15:12:25

yeah, that won't work. set JAVA_HOME

Ben Sless15:12:56

that doesn't work either

Ben Sless15:12:04

JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 clj
Clojure 1.10.1
user=> (System/getProperty "java.version")

Ben Sless15:12:46

my Bash isn't perfect either but I think the JAVA_HOME test is only reachable if JAVA_CMD isn't set

Ben Sless15:12:58

and JAVA_CMD is set if a java executable is found


how about this: PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH clj


straight-up hacking at this point 😉

Ben Sless15:12:08

That's just cheating 🙂 I updated to a newer version of cli tools let's see

Alex Miller (Clojure team)15:12:14

well either set the java you want on your PATH, or set none and use JAVA_HOME

Alex Miller (Clojure team)15:12:10

jenv will work with the former

Ben Sless15:12:14

It would be nice if it could be configured from the command line without resorting to that. It would only require checking if JAVA_CMD is set before setting it for the firs time. Anyway for now I'll use jenv

Alex Miller (Clojure team)15:12:41

how is setting JAVA_CMD different than setting PATH or JAVA_HOME?

Alex Miller (Clojure team)16:12:10

those all feel like identical things configured in the same way from the command line

Alex Miller (Clojure team)16:12:04

I'm not actually disagreeing with your suggestion, just trying to probe the assumptions a bit


I think he’s referring to the section in the clojure script that overwrites JAVA_CMD, so you lose whatever you set on the command line.


That’s the code snippet he posted above.

Ben Sless16:12:31

My rationale is I want to configure different projects to use different versions of the JVM. I can do it with jenv, but what I've been doing with lein is create wrapper scripts where I set in them the JAVA_CMD then use dir-locals in emacs to change the run command for lein

Ben Sless16:12:58

But it looks like jenv will be a better option, all things considered

Alex Miller (Clojure team)16:12:25

we have a ticket for this suggestion already, I'm trying to vet whether having yet one more degree of freedom is necessary

Ben Sless16:12:59

I am biased towards more degrees of freedom. On a practical side: Doesn't require extra tooling: more convenient (less setup), one less dependency. Doesn't require messing around with PATH: safer?

Alex Miller (Clojure team)17:12:23

more degrees of freedom is inherently more complex, so I'm biased against it :)


or you could look at jenv or something similar to more easily switch between java versions

Alex Miller (Clojure team)15:12:49

clj uses JAVA_HOME if you have that set

Noah Bogart16:12:14

I'm having trouble getting a project made with deps.edn to work. i have a src/advent folder with a core.clj file inside. it has a (defn -main ...) function where I do some work. when I run clj -M at the root directory (where deps.edn is) I am dropped into the repl. how do I execute the project like running lein run?


if there's a -main function in namespace advent.core you should be able to do clj -M -m advent.core


also, the new -X will be helpful for running arbitrary functions without having to put a -main in each file as you progress through advent

Noah Bogart16:12:24

is it possible to create an alias to handle that for me?

Noah Bogart16:12:48

yeah, I thought about -X, but I want to be able to say clj -M ... 2 and then it runs day 2's code

Noah Bogart16:12:03

nevermind, I have figured out the alias: :main {:main-opts ["-m" "advent.core"]}

Noah Bogart16:12:43

thank you so much for the help


absolutely. you can acomplish the same with -X as well. the difference is largely -M is tied to a function that must be named -main and gets string args whereas -X can be any function and its options will be passed as edn, which may or may not be better for your use case

Noah Bogart16:12:50

interesting, that's good to know

Noah Bogart16:12:30

gotta pass in the args like -X blah '{:day 1}', right?

Noah Bogart16:12:23

ope, found it. idk why im' struggling so much to read this page, lol

Noah Bogart16:12:24

clj -X:my-fn '[:my :data]' 789

Jeff Evans17:12:57

what am I doing wrong?

user=> (let [nums ["1" "2" "3"]]
          (map Integer/parseInt nums))
Syntax error compiling at (REPL:2:11).
Unable to find static field: parseInt in class java.lang.Integer

user=> (Integer/parseInt "1")


Integer/parseInt is not a function, you can't use it as a value


#(Integer/parseInt %)


@jeffrey.wayne.evans for a little more context, the JVM model is that methods are not values, they can't be placed on the stack or held in a data structure, a clojure function is an object with an invoke method that the clojure compiler looks for, and #() is a terse way to create one to call a specific method


there's a classic essay where someone says "on the JVM methods are slaves to objects and cannot act freely, they must be accompanied by some object at all times", something like that - "kingdom of nouns" I think

😂 3
Noah Bogart21:12:31

good reference, that's a classic

Jeff Evans18:12:41

that makes sense. also probably explains why I can’t do (type Integer/parseInt) (which is what I’d normally do in this situation)


right, it's not a "thing" - it's some action known to a thing


somewhat confusingly the a symbol like Integer/parseInt (a namespace symbol) might be interpreted in a number of different ways


(Integer/parseInt "1") is invoking the parseInt static method on Integer


Math/PI is a reference to a static field PI on the class Math


clojure.core/+ is the value of + defined in clojure.core

Jeff Evans18:12:14

yep, and I can do both type and source on that one


the latter two are "values", they can be passed around as arguments to functions, etc


Integer/parseInt only means something when it is literally the first element of a list like (Integer/parseInt "1")

Jeff Evans18:12:49

so is that a special form? or something else?


it is sort of syntax sugar


Integer/parseInt is a reference to static field parseInt of class Integer disreagards that Integer doesn’t have such field it is still useful to reference realy existing static fields in another classes


exception thrown from the form (map Integer/parseInt ["1"]) should make it clear


user=> (macroexpand `(Integer/parseInt "1"))
(. java.lang.Integer parseInt "1")


(it isn't a macro, it just happens that macroexpansion de-sugars it)

😮 3

btw, more or less complete guide can be found here -


A long time ago (maybe pre-1.0) clojure had two special forms for interop . and new, but then various bits and bobs were added on top of those, so you almost never use those forms directly now. But it can be useful when explaining certain behaviors to show the . or new version.


and for some of the new forms, the meaning can be explained by transforming it in to the . or new version, but the compiler doesn't bother and works directly on the "sugared" form, so is that still syntax sugar?


the interop sugar can also lead to potentially confusing exceptions to clojure's syntax rules

user=> (= Math/PI (Math/PI))


user=> (read-string "#{Math/PI (Math/PI)}")
#{(Math/PI) Math/PI}
user=> (eval (read-string "#{Math/PI (Math/PI)}"))
Execution error (IllegalArgumentException) at user/eval13 (REPL:1).
Duplicate key: 3.141592653589793


Is there any way to destructure atoms in functions in clojure?


Like, the same way you can destructure arrays and maps


the only thing you can do to an atom in a binding context is deref it, you can deref on the right hand of a destructure


so eg. if you have (def a (atom [1 2 3])) you can do (let [[a b c] @a] b)


as for atoms inside other structures: don't, it's not useful


I see. So I would have to do something like:

(defn foo [{:keys [atm1 atm2]}]
  (let [{:keys [a b]} @atm1
        {:keys [c d]} @atm2]
    (+ a b c d)))


Oh, don't put atoms in data structures?


yeah, atoms inside data structures is an antipattern


I mean - there might be some case where it's the best option, but I can't recall seeing one


Okay, so if I have several variables for an applications state, rather than having a set of atoms, I should have one atom which contains a set, yes?


and, for that matter, a nested destructure is usually better split to multiple lines in a let anyway - the performance at runtime is the same, and the code will be clearer


a set of atoms sounds fundamentally unmanageable


@leif yeah, the usual thing is a single atom holding a hash-map


(if you meant that as a literal set instead of just colloquially "some atoms")


right - the mutation of an atom can make it unreachable or disasterous to the internal structure of the set actually it works out because atoms are not compared for value, only by identity


Err.. I meant more of a map of atoms.


Like: {:file (atom "") :color (atom "")}


right, the normal thing is (atom {:file "" :color ""})


But it sounds like instead I should do:

(atom {:file ""
       :color ""})


i figured, just wanted to catch that in the case it was actually meant 🙂.


lol, fair, thanks.


Okay, so what should I do if I want one thing to reference another in the same data structure.


Like...say this:

{:items [_a_ _b_ _c_]
 :selected _c_}


swap! takes a function that receives the atom's state and returns a new state


and note, it's not possible in the general case to do that with sibling atoms. because one atom could change after the read and you'd be working on stale data


Right, I know about swap!, but if I'm making the whole map be an atom, it seems like I can't just rely on it?


i don't know what you mean by "rely on it"


having one atom makes coherent state easier to enforce, not harder


you can set a "validation" function to fix or reject changes for example


i think "possible" versus "impossible"


you can't have a validator that looks at multiple atoms, you can have one that manages a single atom


well - you could in an ad-hoc way but...


Like, if I had a map of atoms, I could do this:

(def state
  {:items [_a_ _b_ _c_]
   :current _c_})

(reset! (:current state) ...)
And now the _c_ in both places would be updated


And I didn't have to manually do any sort of dependency resolution to find other _c_s in the state.


you can't reset a key


@noisesmith Right, I didn't reset the key, I dereferenced _c_ using a key.


oh I misunderstood


but I don't see how multiple atoms makes this any easier


I mean, if _c_ isn't an atom, I can't just call reset! on it, so I'd have to hunt down all of the _c_s in the data structure manually, no?


Like, if I'm making a multi-tab text editor, I can have all of my operations just work on the current tab, and so I can trust that all changes to that one tab's state will end up in the list of tab's state too, since they're literally the same object.


I guess put another way:

(def state
  {:items [_a_ _b_ _c_]
   :current _c_})
(reset! (:current state) _c*_)

;; Results in state turning into:
;;{items [_a_ _b_ _c*_]
;; :current _c*_}


err..that's slightly wrong, actually:


make "current" a path into items rather than a "copy"


in this case I'd use an index, yes


(def state
  (let [c-box (atom _c_)]
    {:items [(atom _a_) (atom _b_) c-box]
     :current c-box}))
(reset! (:current state) _c*_)
;; Results in state turning into:
;;(let [c-box (atom _c*_)]
;;  {items [(atom _a_) (atom _b_) c-box]
;;   :current (atom c-box)})


or have current be the thing being edited and on commit updated items. couple ways to model this


using atoms would be closer to the OO way of doing things. there are UI libraries that help do this in a more functional way (eg., but depending on your project, I'm not sure I would recommend rearchitecting it


@noisesmith Problem with using an index is that program logic that manipulates the :current state also has to be aware of :items. Which is not ideal.


@dpsutton Same with making :current a path.


@leif this implies a larger transformation where there's likely one indexing hash from key to current value, and then at least one other structure describing state / arrangement


but using multiple atoms is not how any normal clojure codebase does things


clojure data structures are designed to hold immutable values, and atoms (though luckily not equal by value), are mutable


@smith.adriane So you're suggesting using subscriptions to watch for changes in _current_?


that would be a pretty big change, so I'm not sure I would recommend it without knowing more about your project and goals


Like, the transformation should be purely functional


But the code should only need to deal with a small part of the data structure.


@leif lenses, as I've seen them are an abstraction over immutable values


And the rest should change with it.


a less invasive change is to pass down event handlers that can delegate changes back up the UI component hierarchy. this reference covers that technique


@leif my experience with clojure doesn't prove you're idea doesn't work, but I can say your design is "weird" for clojure


I mean, I know both C, Java, Haskell, and Racket have things like this, so I'm just assuming Clojure(Script) does too. But it sounds like people don't do it that much. 🙂


I'm pretty interested in functional solutions. I would love any links to references if you have them handy


in fact I'm sure some variation of what you are talking about could work great, it's just not going to be familiar to experienced clojure users (and in a collaborative environment that's a risk)


@smith.adriane Right. Although that seems more like handling the view rather than the view-state. (Which I'm already doing. 😉 )


not sure I follow


Yes it is. 🙂


Just so we're clear, I'm not insisting on doing it one particular way.


Like, the things I want are:


1. A big immutable data structure for my applications state.


2. To have code that does operations on small parts of that state.


3. To have the state stay internally consistent, ideally provided by the language or system, so I don't have to write pub/sub code.


right - this is why I suggested watchers: a function registered via add-watch (you can have as many as you like) can approve or reject changes to a single atom. This doesn't work when state crosses N atoms.

user=> (doc add-watch)
([reference key fn])
  Adds a watch function to an agent/atom/var/ref reference. The watch
  fn must be a fn of 4 args: a key, the reference, its old-state, its
  new-state. Whenever the reference's state might have been changed,
  any registered watches will have their functions called. The watch fn
  will be called synchronously, on the agent's thread if an agent,
  before any pending sends if agent or ref. Note that an atom's or
  ref's state may have changed again prior to the fn call, so use
  old/new-state rather than derefing the reference. Note also that watch
  fns may be called from multiple threads simultaneously. Var watchers
  are triggered only by root binding changes, not thread-local
  set!s. Keys must be unique per reference, and can be used to remove
  the watch with remove-watch, but are otherwise considered opaque by
  the watch mechanism.


I'm happy to do it the idiomatic way in clojure, if there is one. 🙂


(Unless you think that's unreasonable?)


(If so I'm also always happy to learn more.)


I guess another way of putting it: I want to do DAG manipulation with clojure.


And right now the only way of doing it in clojure that I'm aware of is using atoms.


Everything else seems to be purely tree based.


But atoms are certainly more powerful than what I want, as they give you full graph manipulation. And I'm cool with the acyclic part.


I think re-frame's subscriptions is the most popular example of trying to accomplish this. om's cursors and I think are also in the same space


You might look at structuring your state more like a database (a flat set of tuples) instead of an in memory graph of objects

💯 3

So using something like datascript


@smith.adriane Ya, what I want is similar to what re-frame does. Taking a look at hoplin/javelin now.


@hiredman Fair. And I guess SQL like code does make it super easy to make indexes into keys splice well together. I'll take a look at datascript.


to be fair I like the flat database style approach a lot, but have never gone so far as to actually use datascript in a project, I just limp by rolling my own indices, sometimes using clojure.set/index


lol, fair.


I mean, my app's state is actually stored in the browsers local-storage, which is itself a flat key-value it does fit.


Hi, I am exercising on and I don’t understand why the last two forms are testing for false. To me those look binary trees, is there anything I am missing? Thanks


(:a nil ()) i think its complaining that the "right" tree () does not have a value, left and right child. so i guess it depends on if () is a value in itself or a tree that is lacking values


i don't understand why [1 [2 [3 [4 false nil] nil] nil] nil] is not a tree unless false is not a valid value. but i don't see any reason why. but i'm guessing the trees need to be homogenous? the first one is keywords and nils, the remaining ones are integer numbers and nils

👍 3

Is there a good alternative for (count (filter true? (map some-predicate? collection))) ?


this seems like a common thing


@francesco.losciale it seems that they take nil as the only valid leaf

👍 3

@st3fan (count (filter predicate? collection))?


This seems too obvious .. maybe i was over thinking this 🙂


filter true? might be a bit of a foot gun in lots of code since it only recognizes the boolean true and not all truthy things (the complement of the set #{false nil})


identity is the usually thing to filter by


Okay, another question, is there a function like cljs.spec.alpha/keys but allows you to provide defaults?


Like, I want users of the data to be able to rely on the value being there, but the provider of the data can not include it and have the default there.


can you give an example usage?


this sounds like (merge user-provided-value defaults) but that's only toplevel. i think there are several "deep merge" variants in the wild depending on your particular use cases


@dpsutton You're right about the 'deep merge'. Something like this:


and your question about spec was a bit confusing. spec should describe the shape/acceptable values. so if they are optional just make them optional in the spec. then merge the stuff in as needed. if you like you can have a second spec that will spec the fully-fleshed datastructure as well


(def defaults
  {:options {:color "blue"
             :size "big"
             :menu "top"}
  :items []})

(def curr-db
  (atom {:options {:color "yellow"}}))

(_deep-merge_ @curr-db defaults)
#_ {:options {:color "yellow"
              :size "big"
              :menu "top"}
    :items []})


Ya, I considered having two specs, but that seemed to be repeating myself.


Unless there's some sort of splice spec combinator, then I could make an ::items spec and put one in a spec in the :req portion and the other in a spec in the :opt portion.


Anyway, thank you all for your help so today. 🙂

parens 3

@dpsutton Ah, the reason I bundled the default question with spec is because I'd ideally like to associate some kind of 'default' with each spec.


i think you can use conformers to do this but i'm not sure how wise it is


That way I can have my pseudo-types and default-values all in one place, rather than having to write down the name twice.




I'm wondering if conformers are relevant here?


my vague understanding is that a spec's conformer can be used to take apropriate data and make the "canonical" spec-conforming version out of it


in the conformer i think you can turn strings into UUIDs and other such things so i imagine you can merge things into a map


right - that was my thought too, but I haven't used conformers in anger so I'm slightly cautious recommending them here


Ah, and is this why it may not be wise?


(Or are there other issues too?)


we used them at my last job for api shapes but there was a conforming and nonconforming dance to prevent ors and seqs from making them the different shapes


and not often in that code so i really need some examples to mimic rather than do it from scratch


the suggestion here is not to use conformers this way (and not to use specs to coerce data in general)

Alex Miller (Clojure team)23:12:26

using conformers this way is imo wrong and you're wanting something out of spec that it was not designed to provide


@leif and as I read it, that means you should just make a function that converts the data, outside the spec system


or what he said


Okay. So basically what I'm hearing is I should role my own DSL for this. 🙂


(If I want to write my spec and default in the same place)


I think a small handful of functions would suffice, DSL seems a bit much


the most straightforward thing seems to be make your merge function and then a spec that specs that?


@noisesmith Is it possible to use functions here without repeating yourself?


It seems like with functions I have to do something like:

(s/def ::color string?)
(s/def ::size string?)
(s/def ::options (s/keys :req-un [::color ::size]))

(def default-options
  {:color "red"
   :size "large"})


clojure functions (when written / factored properly) don't tend to be very repetitive (beyond the kind of repetition that's desirable, for making intent clear)


I don't consider that code especially repetitive


Note that I had to write color and size 3 times there. Each of which making sense, but I'm following a pattern.


the pattern I'm seeing here is you are quite opinionated about how things should be structured, and clojure is too


Unless you can make a function like this (using pseudo code here)?:

(defn options [opts]
  (doseq [name, spec, default] in opts:
    (s/def `:~name spec)
   {fn []
     (into {}
           (for [name, spec, default] in opts:
              [name default]))))


you'd need a macro for this I think? but yeah, things like this are possible


I find they usually obfuscate more than the enable, but yes they work


> the pattern I'm seeing here is you are quite opinionated about how things should be structured, and clojure is too Eh, I'm less opinionated then I sound. I've been trying to be less aggressive as I talk, with only mixed success. Sorry about that. 🙂


i think one of the design considerations going into spec 2 is to be a bit more manipulable like this.


Oh that would be nice.


i've never used it so just going off a hazy memory of something i might have read, might have made up 🙂


@leif nothing to apologize for, I see most of what I have to offer in this forum as "this is how we'd usually do this in clojure", and if you have a good idea that isn't what we usually do, there's not much more to say there


@noisesmith Makes sense. And that's why I'm asking here. Like, I can always go hack on a thing and make it work (for me), but I also want to absorb some of the community's knowledge. If that makes any sense?)

👍 3

But ya, y'all've been very helpful in giving me stuff to think about, so I really do appreciate it. ❤️


> if you have a good idea that isn't what we usually do, there's not much more to say there not sure I follow. it seems like being able to evaluate different approaches or adapt existing approaches when the use case demands it is a good thing


I think lisps (and clojure) are a great environment for experimenting with new ideas and designs


@smith.adriane as in, I don't go to #beginners to introduce a novel app architecture, and if it works there's no question to answer? I'm trying to talk myself out of the pitfall of spending my time convincing someone to structure an app differently in a channel about learning a new language I guess

👍 3

I could see how, if you're new to clojure, but not new to programming, to not know which questions are which


clojure as a language and as a community has lots of opinions about how programs should be written. especially if you're bringing ideas and practices from another language/ecosystem, I could see how it might be hard to tell if an idea is bad practice, uncommon, or good practice (and it's simply unclear how to implement it because it's a new language)


Yup, that makes a lot of sense.