Fork me on GitHub
#beginners
<
2020-07-03
>
seancorfield00:07:09

Java logging is a gigantic can of worms @stopachka We've gone back and forth on several solutions and we eventually ended up using org.clojure/tools.logging with log4j2 as the implementation and deps for that and the various bridges to route all logging through it. tools.logging can be told to use any of several logging backends so that should probably be your "first stop" on logging. Nearly all of the community logging libraries have a variety of gotchas that ultimately make the logging problem worse, IMO.

👍 1
seancorfield00:07:18

Using slf4j/logback per that blog post is likely to be fine for your needs. I really don't like that slf4j treats FATAL as ERROR and I don't want any XML configuration files so that's why we prefer log4j2 (we can use .properties files, it does auto-loading of changed log files without dropping log items, it supports MDC -- not sure whether slf4j/logback does?)

👍 1
seancorfield00:07:03

But, yeah, to your basic question, using a JVM option to select the appropriate logging context is reasonable. We default to the production log config but override it per-dev with an environment variable or a local JVM option (we run SQL migrations locally with a different log config to when we're running tests, for example).

👍 1
stopa01:07:48

Thanks for the awesome context Sean! Will look deeper into the essay and play around with setting up sans community libraries.

g01:07:54

is there any clean way to inspect the values in a channel before taking from it? or is this getting away from the purpose

g01:07:10

i’d like to see if it either has a certain number of bytes, or whether it contains a delimiter

noisesmith01:07:10

you could implement a custom buffer that offers this, but it also means that using that information in program logic invalidates promises that CSP makes

noisesmith01:07:49

eg. (if (> (messages-count c) 4) ...) - that's a race condition, there's no way to know if the count is for inside the ... code or not

noisesmith01:07:00

all you know is that for a moment that was true

noisesmith01:07:49

if you have a single consumer of the channel in your design, you could use a closed over collection of inputs in a go-loop and hold off execution until the collected data meets some condition

g01:07:34

just to make sure i’m understanding, something like

(go-loop [acc []]
   (conj acc (<!! chan))
   (when stuff
      (handle acc))
   (recur))

g01:07:40

seems like it would work.

noisesmith01:07:57

the acc needs to be an arg to occur, conj doesn't have side effects

g01:07:09

right, yeah

noisesmith01:07:01

(go-loop [acc []]
  (let [acc' (conj acc (<! chan))]
    (if (some-condition? acc')
       (do (handle acc')
            (recur []))
       (recur acc'))))
• fixed typo

noisesmith01:07:32

also note that using <!! inside a go or go-loop block can stall all of core.async, use <! inside go

g01:07:46

brilliant, thank you

Rama10:07:17

Hello all just getting started with Clojurians.

Jonathan Barrenetxea10:07:19

Hi! I'm running into a strange behaviour and I have no clue what could be happening... I have the following reagent component:

(defn FullCalendar [ date-vec
                    events
                    view move-resize-cb
                    dbl-click-cb
                    selection-cb
                    event-select-cb
                    navigation-cb
                    toolbar-component
                    sel-event
                    ]
  (prn "sel-event 1" sel-event date-vec)
  (let [localizer (. BigCalendar momentLocalizer m)]
    (fn [date-vec events view sel-event]
      (prn "sel-event 2" sel-event date-vec view)
      [:div
       [:> DndCalendar {:localizer localizer
                        :date (convert-date date-vec)
                        :events events
                        :views ["day" "week" "month" "agenda"]
                        :view (if (or (nil? view) (empty? view)) "month" view)
                        :titleAccessor #(:int-title (js->clj % :keywordize-keys true))
                        :resizeable true
                        :toolbar true
                        :step 30
                        :timeslots 2
                        :selected sel-event
                        :components {:toolbar (if (nil? toolbar-component)
                                                (reagent/reactify-component Cal-Toolbar)
                                                (reagent/reactify-component toolbar-component))}
                        :scrollToTime cal-start-time
                        :eventPropGetter
                        (fn [event]
                          (if (equal-events? event sel-event)
                            (do
                              (prn "events are equal so selected" event sel-event)
                              "rbc-event rbc-selected")
                            (do
                              (prn "different events" event sel-event)
                              (set-background sel-event))
                            ))
                        :onNavigate navigation-cb

gekkostate11:07:57

Your (fn) params need to match your (defn) params. I suspect, your sel-event in your fn is actually dbl-click-cb. Have a look at https://github.com/reagent-project/reagent/blob/master/doc/CreatingReagentComponents.md for reagent.

noisesmith16:07:02

as a style rule, if you have more than 4 args, it's best to use a hash-map with keywords instead of a long arglist

noisesmith16:07:38

(some people disagree on whether "4" is the magic number, I think there's consensus that the magic number is less than 9)

Jonathan Barrenetxea07:07:10

Sorry for the late response... Yes @U5DFXKYSV, you are right, I forgot that the call to the second fn is called exactly the same way as the main defn

Jonathan Barrenetxea07:07:51

with the same params I mean

Jonathan Barrenetxea07:07:59

Good point about the number of args, @noisesmith, I think I might have to refactor some of the functions in my code now that you mention this

Jonathan Barrenetxea10:07:25

sel-eventis coming from a re-frame subscription in the call to this component. The thing that I don't understand is that in the first prn I can see the correct value of sel-event (a map with different fields), but when I pass it into the fn the second prnand the rest of the calls treat it as a #object[Function]

Jonathan Barrenetxea10:07:14

Why can that be? Why is this map treated as a function inside of the render fn ? Thanks!

Timofey Sitnikov12:07:05

Good Morning Clojurians. I am trying to convert the https://github.com/seancorfield/usermanager-example to work with Postgres, but I am getting an error:

HTTP ERROR 500 org.postgresql.util.PSQLException: ERROR: operator does not exist: integer = character varying Hint: No operator matches the given name and argument types. You might need to add explicit type casts. Position: 28
(The full error listing is attached) My last hook to libraries function:
(ns bubbleuptop.model.user-manager
            [next.jdbc.sql :as sql])

(defn delete-user-by-id
  "Given a user ID, delete that user."
  [db id]
  (sql/delete! (db) :users {:id id}))
When I change the last line to (sql/delete! (db) :users 1)) it works fine and deletes the user with ID of 1. Also as long as I do not have to pass any numeric parameters to DB it seems to work fine. Am I doing something wrong?

walterl13:07:51

It looks like the id you received in delete-user-by-id is a string, and not an int

sove14:07:08

This often happens at the protocol border, it it expecting an INT but it got a CHAR

sove14:07:01

Works when you pass an explicit INT, but not when you pass a string or char, so you gotta find a way to convert or Cast that string/char into an int datatype for postgresql

sove14:07:25

I get these errors sometimes when working with HTML input / form input... sometimes you need to specify exactly in the HTML form type="number"

sove14:07:48

if it's on the clojure side, you may need to cast to integer .. i think it's Integer/parseInt

1
g15:07:19

hey everyone, how can i get the byte form of ÿ ? (extended ascii)

andy.fingerhut15:07:07

Do you mean the Unicode code point integer value? Or a sequence of bytes that Unicode is represented in UTF-8? In UTF-16? Something else?

g15:07:42

unchecked-byte throws character cannot be cast to number

andy.fingerhut15:07:50

Is the error message you see: "Value out of range for byte: ÿ"

andy.fingerhut15:07:29

The numeric range for a JVM byte is -128 to +127. That Unicode code point for that character is not in that range. Try int

g15:07:17

oh yeah! that did it

andy.fingerhut15:07:20

Or unchecked-int if you prefer

g15:07:23

thanks!

noisesmith16:07:56

more generally, given an encoding, you can get the sequence of 8 bit signed values that would be interpreted as that value in that encoding - characters don't have bytes, but encodings do map characters to sequences of bytes

user=> (->> ["US-ASCII" "UTF-8" "UTF-16" "UTF-32" "Big5" "IBM-Thai"]
     (into {} (map (fn [encoding]
                     (let [decoded (.getBytes "ÿ" encoding)]
                       [encoding
                        {:bytes (seq decoded)
                         :reconstructed (String. decoded encoding)}]))))
     (pprint))
{"US-ASCII" {:bytes (63), :reconstructed "?"},
 "UTF-8" {:bytes (-61 -65), :reconstructed "ÿ"},
 "UTF-16" {:bytes (-2 -1 0 -1), :reconstructed "ÿ"},
 "UTF-32" {:bytes (0 0 0 -1), :reconstructed "ÿ"},
 "Big5" {:bytes (63), :reconstructed "?"},
 "IBM-Thai" {:bytes (63), :reconstructed ""}}
nil
unchecked-int is doing something weird, 255 is not the right answer • edited to expose the encodings that fail to contain that character

noisesmith16:07:20

@gtzogana - note that ÿ is not "extended ascii", and AFAIK there is no such encoding - according to unicode "ÿ" is a code point, and an encoding decides which bytes (and how many, and in what order) correspond to that code point

noisesmith16:07:35

@gtzogana and I was partially wrong above - 255 (int) is the same as (-61 -65) according to the jvm, but this depends on things like the jvm's default encoding, and byte order, which are implementation details that are bad to rely on

noisesmith16:07:55

(unless your question is very specifically and only about one version of java, because it's allowed to change those things AFAIK)

Rabie16:07:17

Hello, So I created a coljure-script project based on a reframe template and added calva and re-frame-10x. I used this command: lein new re-frame py_project +calva +10x To run and test my project I use the "jack in" REPL in calva and it works great. However I noticed that +10x reframe tracing slows down a bit my execution. Is there a way to elegantly remove/deactivate 10x from the lein project? What I would do otherwise is create a new lien template without +10x and copy paste all my .cljs files. Thank you for your help

phronmophobic17:07:47

you might be able to simply get away with modifying or removing these 2 key value pairs in your project.clj:

:closure-defines      {"re_frame.trace.trace_enabled_QMARK_" true}
 :preloads             [day8.re-frame-10x.preload]

Rabie18:07:16

@U7RJTCH6J It worked. Actually I did set true to false but I didn't notice the :preloads part. Thank you very much!

bananadance 1
phronmophobic18:07:32

there's also a more advanced setup possible where you use profiles to turn 10x on and off

👍 1
🙏 1
vlad_poh17:07:29

Clojure sages how do i schedule functions to run at certain times. Library recommendations or roll my own?

Vachi17:07:00

you can use Java ScheduledExecutor or Quartz

Chase18:07:21

What's the practical differences between creating a var using def say of a map and having the same map but it being a function using defn with no input parameters?

seancorfield18:07:47

With def it will be evaluated at load/compile time. With defn it will be "evaluated" at call time (although it will still be compiled at load time). So any differences will depend on what is in the hash map.

Chase18:07:05

As an example from Clojure From the Ground Up:

(defn atlas-v
 []
  {:dry-mass  50050
   :fuel-mass 284450
   :time 0
   :isp 3050
   :max-fuel-rate (/ 284450 253)
   :max-thrust 4.152e6})

seancorfield18:07:27

The other difference is for REPL workflows: references to a def will be compiled in with the initial value, so if you change the def in the REPL, references to it will still refer to the old value. If you use a defn, each call will always produce the latest value of the function.

seancorfield18:07:02

(you can get around that by using #'atlas-v instead of plain atlas-v and then you can change the def and its new value will be picked up)

seancorfield18:07:21

For a hash map like that which is all constants, I'd use def.

Chase18:07:00

Ok, cool. Thanks for the clarifications. I'm a little stoked I was thinking the same with it just being constants which is why I asked. Figured I was missing some kind of functionality

seancorfield18:07:17

Perhaps it's a nod to the future where the Atlas V data might come from somewhere else (but then I would expect it to have some sort of argument that could be queried to find where to get the data).

seancorfield18:07:32

0-arity functions are nearly always impure, unless they are deliberately recomputing something on every call (such as a lazy sequence so you can consume it without holding onto the head).

Lukas18:07:31

Hey, is there a way to inspect anonymous function?

Lukas18:07:32

e.g. find out what a function like this #function[namespace/eval14220/fn--14227] does.

seancorfield19:07:08

You can't really "inspect" a function since each function is compiled to a class with an .invoke() method.

seancorfield19:07:28

What you can do to make some things more readable is using (fn [..] ..) instead of #(..) or (partial ..) or (comp ..) and then giving a name to the otherwise anonymous function (fn some-name [..] ..) and then at least some-name will show up in the compiled name and in stacktraces.

❤️ 1
kingmob20:07:47

@UP2FUP0TT, there’s a little mini-library that attaches a fn’s form as metadata to itself for printing, https://github.com/technomancy/serializable-fn, but I’m not sure it’ll apply to reader literal fns. Still, it might be worth a look while learning.

Lukas21:07:56

Thank you guys :hugging_face:

mathpunk19:07:50

I want to learn how to read the output of spec/explain-data. I'm sure this is full of useful information but I couldn't make heads or tails of it... it's from looking at cider inspect last result

Class: clojure.lang.PersistentArrayMap
Contents: 
  :clojure.spec.alpha/problems = ( { :path [], :pred clojure.core/map?, :val [ :com.logicgate.helpER.source/indent 4 ], :via [ :com.logicgate.helpER.specs/scenarios :com.logicgate.helpER.specs/scenario :com.logicgate.helpER.specs/specification ], :in [ 0 0 ] } { :path [], :pred clojure.core/map?, :val [ :com.logicgate.helpER.source/tag "describe" ], :via [ :com.logicgate.helpER.specs/scenarios :com.logicgate.helpER.specs/scenario :com.logicgate.helpER.specs/specification ], :in [ 0 1 ] } { :path [], :pred clojure.core/map?, :val [ :com.logicgate.helpER.source/message "Adding a user in an SSO environment" ], :via [ :com.logicgate.helpER.specs/scenarios :com.logicgate.helpER.specs/scenario :com.logicgate.helpER.specs/specification ], :in [ 0 2 ] } { :path [], :pred clojure.core/map?, :val [ :com.logicgate.helpER.source/level 0 ], :via [ :com.logicgate.helpER.specs/scenarios :com.logicgate.helpER.specs/scenario :com.logicgate.helpER.specs/specification ], :in [ 0 3 ] } { :path [], :pred clojure.core/map?, :val [ :com.logicgate.helpER.source/indent 6 ], :via [ :com.logicgate.helpER.specs/scenarios :com.logicgate.helpER.specs/scenario :com.logicgate.helpER.specs/specification ], :in [ 1 0 ] } ... )
  :clojure.spec.alpha/spec = :com.logicgate.helpER.specs/scenarios
  :clojure.spec.alpha/value = ( { :com.logicgate.helpER.source/indent 4, :com.logicgate.helpER.source/tag "describe", :com.logicgate.helpER.source/message "Adding a user in an SSO environment", :com.logicgate.helpER.source/level 0 } { :com.logicgate.helpER.source/indent 6, :com.logicgate.helpER.source/tag "describe", :com.logicgate.helpER.source/message "Navigate to Admin -> Users", :com.logicgate.helpER.source/level 1 } { :com.logicgate.helpER.source/indent 8, :com.logicgate.helpER.source/tag "describe", :com.logicgate.helpER.source/message "Click on \"+ New User\" button", :com.logicgate.helpER.source/level 2 } { :com.logicgate.helpER.source/indent 10, :com.logicgate.helpER.source/tag "describe", :com.logicgate.helpER.source/message "the \"Send Welcome Email\" checkbox input", :com.logicgate.helpER.source/level 3 } { :com.logicgate.helpER.source/indent 12, :com.logicgate.helpER.source/tag "it", :com.logicgate.helpER.source/message "is displayed", :com.logicgate.helpER.source/level 4 } ... )

mathpunk19:07:37

I ended up solving my problem by zooming my attention toward a smaller part of what i was working on

mathpunk19:07:52

but, i'd love to understand what Lassie was trying to express...

seancorfield19:07:18

In the above, it's saying "expected map, found ..." for each of those values.

seancorfield19:07:19

The :pred says what test failed, :val tells you what it tried to test, and :via gives the "call tree" through the specs.

seancorfield19:07:27

:com.logicgate.helpER.specs/scenarios :com.logicgate.helpER.specs/scenario :com.logicgate.helpER.specs/specification so scenarios is (I assume) a collection of scenario and that has a specification which I expect is s/keys (most likely, given the map? predicate failure).

seancorfield19:07:02

and your overall value was a sequence of hash maps -- scenarios, I assume, so the problem is that a value like { :com.logicgate.helpER.source/indent 4, :com.logicgate.helpER.source/tag "describe", :com.logicgate.helpER.source/message "Adding a user in an SSO environment", :com.logicgate.helpER.source/level 0 } fails the scenario spec because the :in suggest collection navigation (`0`th element, then 0th element of that, which is a MapEntry -- the [ :com.logicgate.helpER.source/indent 4 ] pair -- and it is expecting an s/keys so it should be a hash map.

seancorfield19:07:16

So I think the level of nesting is off -- either in the Spec or in the data structure.

mathpunk20:07:02

I'll need to reflect quietly on it for a minute but I think so, thx Sean

mathpunk20:07:06

:via gives a key path and :in gives a numeric index path, then?

seancorfield20:07:23

:in matches get-in so it would be a combination of keys and index values

seancorfield20:07:55

:via is the path through the chain of Specs, rather than through the values (which is :in)

vncz21:07:59

Anybody willing to help a Clojurian newbie here? I am trying to implement some typical coding questions to get up to speed. I'm struggling a bit with the typical of computing the depth map of a binary tree 4 / \ 7 9 / \ \ 10 2 6 \ 6 \ 2 Implementing this in TypeScript was really trivial, but it relies on the global map being passed and modified around:

vncz21:07:54

I wanted to try to implement it in Clojure trying to stay immutable as much as possible, but I am struggling. Anybody has any advice?

Daniel Stephens23:07:01

(defn depth-seq
  [tree]
  (letfn [(children [n] (concat (keep :right n) (keep :left n)))]
    (->> (iterate children [tree])
         (take-while seq)
         (map (partial map :node)))))
Thought I'd try a solution as well since I'm still learning 🙂 I quite like when I can use iterate for things, though the last map is little ugly unfortunately! The idea here is that iterate applies a function to the previous value to get the next one each time, so if we start with a list of nodes [tree] we can map over them to get the children, and we can do that recursively! That take-while is so that we stop when we reach the bottom (no more children exist). In some cases the last map might not be desirable as without it you keep more context, but with it is closer to your TypeScript solution

vncz14:07:24

Oh interesting, let's check this out!

vncz14:07:54

Well there are a lot of things I am still uneducated about here! THat's good

vncz14:07:55

There's probably a small bug in your implementation since it returns nil , I'll try to take a look!

vncz14:07:33

Ok I guess its' (partial map :value)

vncz14:07:33

> though the last map is little ugly unfortunately! Now I understand why it was required though! Nice stuff

👍 1
andy.fingerhut21:07:45

The depth, meaning the maximum number of steps from the root to any of the leaves?

andy.fingerhut21:07:10

So 4 in your example tree of your figure?

vncz21:07:31

@andy.fingerhut Thanks for chiming in! Not exactly. The idea is to group all the items by the depth in the tree

vncz21:07:36

So the output would be something like this:

vncz21:07:18

On level 0 we have 4, on level 1 we have 7 and 9… and so on

vncz21:07:52

I guess this is not really a Clojure specific question, it's more about how to implement this without using mutable data structures

andy.fingerhut21:07:22

So you want to return a map whose keys are the depths of all nodes, and the value corresponding with each key is a list of nodes with that depth?

vncz21:07:29

Correct!

noisesmith21:07:30

the general concept is to use an accumulator holding things that get updated, and pass it to a recursive call or reduce step

vncz21:07:50

@noisesmith So…reduce all the way down?

noisesmith21:07:02

I'll have a one liner in a moment

🕐 1
vncz21:07:35

Looking forward. I've been thinking about this for a while and I can't figure that out

noisesmith22:07:13

(def tree
  [{:node 4
    :children [{:node 7
                :children [{:node 10
                            :children [{:node 6
                                        :children [{:node 2
                                                    :children []}]}]}
                           {:node 2
                            :children []}]}
               {:node 9
                :children [{:node 6
                            :children []}]}]}])

(defn depth-seq
  [tree]
  (lazy-seq
   (when (seq tree)
     (cons (map :node tree)
           (depth-seq (mapcat :children tree))))))
user=> (depth-seq tree)
((4) (7 9) (10 2 6) (6) (2))

noisesmith22:07:25

the list is the nodes in order of depth

noisesmith22:07:42

I thought it was going to be a one liner, but really it's clearer on 6 lines

noisesmith22:07:50

as usual my estimate for how long my work would take was wildly wrong

😁 1
noisesmith22:07:46

if you want a different representation for the tree, just replace :node and :children with the functions that get those respective parts of each branch

vncz22:07:18

Yeah so the data structure I have has left and right keys explicitly but that should not be a problem

vncz22:07:28

Thanks for the tip, I'll check out the code There's a lot of stuff I do not know about

noisesmith22:07:32

in that case, you can replace :children with a function returning the tuple of [r,l] - the rest should work as is

noisesmith22:07:33

feel free to ask about the functions / macros used, and the idioms, there are plenty of common but relatively unique clojure tricks in that small program

noisesmith22:07:05

I would have finished this faster but when I was 95% done with the recursive accumulating version I realized it was better done as a breadth first search

vncz22:07:09

Why the use lazy-seq?

noisesmith22:07:40

because idiomatically if the only data dependency for the next item to produce is the previous item, a lazy-seq is the best representation

noisesmith22:07:20

this would allow traversing a very deep tree breadth first (one level at a time going down), without filling the RAM, for example

noisesmith22:07:10

though if the primary usage would be indexed lookup of items by depth, you could use a vector instead of a lazy-seq

vncz22:07:27

Well these 6 lines are very…dense. I still do not get what's going on 🙂

noisesmith22:07:58

lazy-seq is a macro that takes form, and returns something you can call to evaluate the compiled form, which caches it the first time it is accessed

noisesmith22:07:22

so the body is executed only once, and not until you ask for it

noisesmith22:07:41

when is like if, but it only has one branch, and returns nil if the branch returns false

noisesmith22:07:03

seq returns nil, which is treated as false, if presented an empty collection

noisesmith22:07:27

cons takes a "head" and a "tail" (it's short for construct), it returns a linked list comprising that head and tail

vncz22:07:38

Well yes I get the individual functions

vncz22:07:43

Just trying to understand the logic and the flow

noisesmith22:07:25

well if explaining start to end of the body is tedious, perhaps you have concrete questions?

vncz22:07:43

Still going through the code. I guess the secret sauce is mapcat 🙂

vncz22:07:58

Ok I can see you defined the nodes as an array with a single map inside

vncz22:07:07

That I do not understand but I am assuming it's to make it work with mapcat

noisesmith22:07:02

I did that so that the initial call to the function and the recursive calls would use the same data representation

noisesmith22:07:24

mapcat is needed because the children of 7 and the children of 9 are treated as coequal siblings • edit wrong number was here

noisesmith22:07:32

so I concatenate them into one result

noisesmith22:07:23

IOW it defines a "tree" that is really a forest: 0 or more trees which are walked in parallel to group nodes by depth

noisesmith22:07:59

and IMHO the real magic comes from using data structures and function calls that are "sympathetic" to that pattern

noisesmith22:07:38

of course you could make a helper that only accepts a tree, but to me that's more complicated than putting a tree in a vector producing a forest

noisesmith22:07:14

(or wrapping it in whatever other "forest" representation your structure uses - I guess it's actually a right/left pair)

vncz13:07:32

@noisesmith Would this be a correct spec for the data structure we're using above?

vncz13:07:07

My doubt is that s/coll-of wants a predicate, and I do not understand how to get a predicate our of a registered spec

andy.fingerhut21:07:33

So one approach is to somehow return a list of maps where each has the depth of a node, and the node, e.g. {:depth 1 :node <whatever you want to use to represent a node>}. Then call (group-by :depth foo), where foo is that list of maps.

vncz21:07:29

Oh interesting, I did not know about group-by . I'm really not that familiar with the list library

andy.fingerhut21:07:17

There is certainly a lot in Clojure's core library, so don't expect to learn it all at once. The Clojure cheatsheet tries to group things with related functionality together, which can help when learning about similar things: http://jafingerhut.github.io/

andy.fingerhut21:07:46

Also this version http://clojure.org, but it doesn't have the hovering tool tips

andy.fingerhut21:07:11

Never mind. It does. But I like the other version better, personally.

andy.fingerhut21:07:54

For the 'somehow' step that comes first, you can use a recursive function that take a parameter current-depth, and returns {:depth current-depth, :node current-node}, and append that to the list returned by a recursive call on all children.

vncz21:07:55

Here's the issue I'm having. I do not want to mutate anything so adding a map = creating a new map

vncz21:07:13

When I'm calling recursively the function on the left and the right element, I would get two new maps and they should get merged somehow?

rig0rmortis22:07:12

how do folks configure library behavior? for example, I'm writing a tiny logging library to teach myself clojure and I'd like the user to be able to configure certain behavior once, e.g. set a boolean to true or false I found dynamic variables (`def ^:dynamic foo ...`) but wanted to avoid having the user wrap every call in a binding

rig0rmortis22:07:01

right now my code has this hardcoded at the top:

(def config
  {:level  :debug
   :stdout true
   :file   nil
   :pretty false})
and these are things I'd like the user to configure once I found some libraries like https://github.com/yogthos/config

noisesmith22:07:58

you only need binding if you want different values in different contexts, you can use set! on the root value

rig0rmortis22:07:24

ah, lemme give that a shot - thank you

noisesmith22:07:33

see for example *warn-on-reflection*, *out* *print-length* etc. offered by clojure - people usually set them once if they want a non-default value, or rarely they use binding, both work

rig0rmortis22:07:07

awesome, I'll check those out - thanks

rig0rmortis23:07:50

I seem to be unable to change with set! and get:

Can't change/establish root binding of: *pretty* with set
where I have:
(def ^:dynamic *pretty* false)
(set! *pretty* true)

rig0rmortis23:07:08

I can, however, use set! on *warn-on-reflection*

noisesmith23:07:16

oh, I misremembered how to set this up you can only use set! inside a binding context, I forgot that the repl itself was inside a binding context - but outside the context you can use the value provided via the initial def

noisesmith23:07:41

(cmd)user=> (def ^:dynamic foo false) #'user/foo (ins)user=> foo false (ins)user=> (binding [foo true] (set! foo :file-not-found) foo) :file-not-found (ins)user=> foo false

rig0rmortis23:07:38

hm not sure I follow, in the example above, the next invocation of `*foo*` shows false

noisesmith23:07:29

right, because set! only works inside binding - you can use alter-var-root to set a root binding though

rig0rmortis23:07:30

so if my code is relying on the user having configured *foo* , dynamic vars probably won't work here? is that accurate?

noisesmith23:07:06

(ins)user=> (alter-var-root #'*foo* not)
true
(ins)user=> *foo*
true

rig0rmortis23:07:21

is there recommended best practice here? i.e. is what I'm doing discouraged 🙂

rig0rmortis23:07:35

or is this a fairly clojuresque way of doing things

noisesmith23:07:36

it's a slightly odd API, but using a function encourages safer coding patterns

rig0rmortis23:07:01

I could wrap the alter-var-root in a function called "toggle" or something like that?

noisesmith23:07:06

all of clojure's mutable containers are modified by providing a function that takes the previous value and returns the new one

noisesmith23:07:37

sometimes you just want (alter-var-root **foo** (constantly true))

noisesmith23:07:57

but constantly means you are ignoring any changes other threads might be trying to make

rig0rmortis23:07:50

oh neat, yeah I think that's what I want considering this is kind of analogous to an "init" that's unchanged through the rest of the process/application