Fork me on GitHub
#beginners
<
2020-06-11
>
adam03:06:02

How do I drop :email map from [{:name '(:value-required)} {:email ()}] since its value is empty?

raspasov04:06:19

(update-in 
  [{:name '(:value-required)} {:email ()}] 
  [1] 
  (fn [m] (dissoc m :email))) 
=> [{:name (:value-required)} {}]

amirali05:06:00

I believe u can do drop-last as well. As email is second element of your vector :

(def m (drop-last 1 [{:name '(:value-required)} {:email ()}]))
=> ({:name (:value-required)})

seancorfield03:06:00

@somedude314 dissoc will remove a key from a map.

valerauko04:06:53

maybe you're looking for something like this?

(defn any-present?
  [data]
  (let [values (vals data)]
    (not-every? empty? values)))

(let [value [{:name '(:value-required) :foo ()} {:email () :bar ()}]]
  (->> value
       (filter any-present?)
       (into [])))

adam13:06:22

Thank you @UAEH11THP. not-every? was what I am looking for

👍 3
coetry04:06:23

One thing that i find kind of tedious is specifying the latest version of a dependency. Coming from a js background where we can do something like npm i react and it will automatically create a package.json with the latest version of react in it ... is there something similar for clj/cljs?

seancorfield04:06:34

@coetry It's not recommended -- because of reproducibility concerns -- but you can use "RELEASE" as the version and you'll get the latest stable release of a library.

👍 4
seancorfield04:06:12

As an example

[email protected]:/mnt/c/Users/seanc/clojure$ clj -Sdeps '{:deps {selmer {:mvn/version "RELEASE"}}}'
Downloading: selmer/selmer/maven-metadata.xml from clojars
Downloading: selmer/selmer/1.12.27/selmer-1.12.27.pom from clojars
Downloading: json-html/json-html/0.4.7/json-html-0.4.7.pom from clojars
Downloading: com/fasterxml/jackson/core/jackson-core/2.9.6/jackson-core-2.9.6.jar from central
Downloading: json-html/json-html/0.4.7/json-html-0.4.7.jar from clojars
Downloading: selmer/selmer/1.12.27/selmer-1.12.27.jar from clojars
Clojure 1.10.1
user=>
1.12.27 is the current stable release of Selmer.

coetry04:06:11

Do clojure devs mainly do lookups on clojars to determine the latest version and only upgrade as needed?

coetry04:06:22

i can imagine this getting complex as a project grows in dependencies

seancorfield04:06:18

Maven and/or Clojars, yes. Or on the GitHub page of the project.

👍 4
seancorfield04:06:23

But there's a preference for reproducible builds so it's OK to use RELEASE for dev tools sometimes but not for builds you actually want to take to production -- we value stability.

seancorfield04:06:01

FWIW, I use "RELEASE" a lot for quick dev exploration and testing, and most of my "dot clojure" file uses "RELEASE" deliberately to always pull in the latest versions of dev/test tooling.

coetry05:06:29

that's very helpful! thank you Sean 😄

valerauko04:06:44

also there is lein-ancient which lets you check if anything is outdated

valerauko04:06:54

just like npm outdated

seancorfield04:06:18

Depot is an equivalent for the Clojure CLI / deps.edn

amirali05:06:55

hi folks. how can i compile a block of code that i get as user input?

mruzekw05:06:01

read-string and eval

🎯 12
Hlodowig06:06:04

Related question... I've tried this several ways to no avail:

(ns test.core
  (:gen-class))

(defn -main []
  (load-string (slurp "/Users/hlodowig/stuff.clj"))
  (println stuff))
stuff.clj
(def stuff {:a 1 :b 2 :c 3})
I'm getting Syntax error compiling at (test/core.clj:6:3). Unable to resolve symbol: stuff in this context

Hlodowig06:06:46

Either I'm misunderstanding read-string completely or I'm making a very basic mistake. TIA.

mruzekw06:06:35

Looking...

mruzekw06:06:59

Ah, well, it's a syntax error

mruzekw06:06:21

So we're not even getting to the point where load-string and slurp are being used

mruzekw06:06:39

Looks like the compiler expects stuff to already be defined

mruzekw06:06:12

But I do wonder if you could just define stuff with a stand in value, and then let stuff.clj override it

mruzekw06:06:42

That' way it will compile and use the new value

mruzekw06:06:37

At the same time I'm not sure if this is best practice. I can't say for sure though cause I don't know your goal. I imagine something like load-string is best used in the REPL.

Hlodowig06:06:10

I'm trying to have some sort of simple external configuration.

seancorfield06:06:34

Use EDN instead of trying to write config-as-code.

seancorfield06:06:02

Configuration should be "just" a data structure. In your example, stuff.edn would contain {:a 1 :b 2 :c 3} and you would use clojure.edn/read-string -- https://clojure.github.io/clojure/clojure.edn-api.html#clojure.edn/read-string -- to turn it into Clojure data

Hlodowig06:06:12

:thinking_face: Neat. I will take a look. Thank you guys.

mruzekw05:06:55

(eval (read-string "(+ 1 1)"))

Hlodowig06:06:04
replied to a thread:read-string and eval

Related question... I've tried this several ways to no avail:

(ns test.core
  (:gen-class))

(defn -main []
  (load-string (slurp "/Users/hlodowig/stuff.clj"))
  (println stuff))
stuff.clj
(def stuff {:a 1 :b 2 :c 3})
I'm getting Syntax error compiling at (test/core.clj:6:3). Unable to resolve symbol: stuff in this context

valerauko06:06:35

is there a way to alias a macro? (i'd want to alias cljs.core.async.interop/<p! as await)

dpsutton06:06:34

cljs.user=> (require '[clojure.core.async.interop :as interop :refer [<p!] :rename {<p! await}])
nil
cljs.user=> (source await)
(defmacro <p!
  "EXPERIMENTAL: Takes the value of a promise resolution. The value of a rejected promise
  will be thrown wrapped in a instance of ExceptionInfo, acessible via ex-cause."
  [exp]
  `(let [v# (cljs.core.async/<! (cljs.core.async.interop/p->c ~exp))]
     (if (and
          (instance? cljs.core/ExceptionInfo v#)
          (= (:error (ex-data v#)) :promise-error))
       (throw v#)
       v#)))
nil
cljs.user=>

valerauko07:06:26

i didn't know about the rename part, awesome thanks!

Daniel Östling06:06:16

What's the idiomatic way of doing something like this idea, where I need to select the function to apply for next loop iteration? Should I put the test+function selection in a separate let before recur not to break tail call optimization?

(loop [fn-to-apply 'fn1
       items items-to-process]
  (let [[item & remaining-items] items
        apply-result (fn-to-apply item)]
    (if (apply-ok? apply-result)
      (recur 'fn1 remaining-items)
      (recur 'fn2 remaining-items))))
Beware, I just banged this out on the keyboard, haven't checked for syntax errors etc. Just as an idea illustration. :)

andy.fingerhut06:06:39

You do not need quotes before fn1 or fn2 in that code.

andy.fingerhut06:06:24

The Clojure compiler will always tell you if recur cannot be compiled because it is not in tail position. Those example should both be in tail position.

andy.fingerhut06:06:43

That particular code is an infinite loop, unless something throws an exception, by the way, since all cases recur

andy.fingerhut06:06:26

You could also replace the entire (if ...) with something like this, if you like: (recur (if (apply-ok? apply-result) fn1 fn2) remaining-items)

Daniel Östling07:06:43

Yeah, thanks. I wasn't sure of the "right" way to do something like this 🙂

Daniel Östling07:06:07

And yes, I need a base case for sure 🙂

Aviv Kotek08:06:15

hi, how would you do your schema migrations in a clojure-app? i'm familiar with https://flywaydb.org/ which has java api, anyone used https://github.com/weavejester/ragtime or https://github.com/yogthos/migratus

valerauko09:06:33

yeah i use ragtime for hobby and work too

Aviv Kotek09:06:15

I see it is not maintained for a while, does it have any audit feature? to see all db executions

valerauko09:06:17

it stores which migration was applied when, but that's all i think

valerauko09:06:27

probably not sufficient for a strict audit

Aviv Kotek09:06:43

so what are your main usages of ragtime? in workplace*

valerauko09:06:20

uh, migrate the database without having to manually run sql. we don't have strict audit requirements regarding database schema (yet)

👍 3
dharrigan10:06:37

I use flyway with Clojure

dharrigan10:06:40

It's dead simple

Aviv Kotek12:06:40

I see it's not possible to rollback in the free-edition

Aviv Kotek12:06:43

how do you handle that?

dharrigan12:06:07

I don't rollback

dharrigan12:06:26

I test test test first locally, confident that later I won't need to rollback

dharrigan12:06:32

I can't recall a time I had to rollback.

valerauko12:06:58

i figure if you have serious audit reviews "confidence" won't be sufficient

dharrigan12:06:55

One is never 100% confident with software. It could fail at any point. However, I ensure that I've tested to an inch of its life locally.

dharrigan12:06:00

I handle volumes of data that is approaching nearly 1 million entries per day

Aviv Kotek12:06:07

I see that the other lib's deal with rollback just as they perform the opposite-execution

dharrigan12:06:16

And it's pretty damn important data too - insurance data.

dharrigan12:06:39

I'm pretty confident 🙂

dharrigan12:06:02

I don't see how that oposite will work well

Aviv Kotek12:06:13

those libs do up-down with create-drop

Aviv Kotek12:06:28

an 'undo' of 'flyaway' isn't really a rollback then

Aviv Kotek12:06:43

you have to write the .sql file yourself

dharrigan12:06:50

If a n other lib does a drop of a table

dharrigan12:06:54

how do you roll back that?

Aviv Kotek12:06:40

i'm just saying that the 'undo' in flyway is not a rollback

dharrigan12:06:48

sql migrations, no matter the tech, are always fraught with the potential of scrwing your db up tremendously 🙂

Aviv Kotek12:06:49

youv'e been using the 'undo' then?

dharrigan12:06:55

never used the undo

dharrigan12:06:59

never had to

dharrigan12:06:33

Flyway does point out the caveats of undo

👍 3
dharrigan12:06:49

Caveat emptor 🙂

dharrigan12:06:11

Right now I'm in mongo territory

dharrigan12:06:19

I'm not fond 🙂

dharrigan12:06:51

or rather 😞

valerauko09:06:29

what's the clojurescript equivalent of import Foo from 'bar' ? i can't seem to get it working

fabrao09:06:55

["bar" :default Foo]

fabrao09:06:11

you have this reference in shadow-cljs docs

valerauko09:06:57

that is exactly what i've been looking for, thank you

valerauko10:06:53

umm i still can't get it working. i'm trying to use this react component from re-frame: https://github.com/jakezatecky/react-checkbox-tree#render-component i :require ["react-checkbox-tree" :default CheckboxTree] but then

(def checkbox-tree
  (reagent/adapt-react-class CheckboxTree))
yells at me saying "Component must not be null c". what am i doing wrong?

Harshana11:06:48

Hey. I was facing the same error too. Try this

(:require
 ["react-checkbox-tree" :as CheckboxTree])
(defn checkbox-component []
  [:> CheckboxTree
                  {:nodes [{:value "a" :label "b"}]}])

Harshana11:06:42

Call (checkbox-component) to render it.

valerauko11:06:25

indeed that got it to work. any idea what could be the problem?

Harshana11:06:15

It should be defn and not def 'cos when I used def, it said expecting a function. Also the react-checkbox-tree expects :nodes as required property.

valerauko11:06:55

did you manage to get it to open/close?

valerauko12:06:38

needed to pass the values to on-check and on-expand like [:branch-check %]

sova-soars-the-sora13:06:12

Morning All 😃

morning 3
adam14:06:44

How do I test a function is returning a specific map in (testing ... (is (= (func args) {:key :val} doesn't seem to work

valerauko14:06:55

it should work. can you post your specific example?

mloughlin14:06:35

what's the error?

adam15:06:59

Sorry false report. My function had some problems. I got confused because (is) wasn't giving me the actual output.

amirali14:06:57

Is there anything that could help me get vector elements out of it? for example if I (conj [1 2 3] [4 5]) the result would be [1 2 3 [4 5]]. while what i want from conj is [1 2 3 4 5]

fricze14:06:55

you probably want to use concat?

fricze14:06:08

or flatten?

dpsutton14:06:28

(apply conj [1 2 3] [4 5])

dpsutton14:06:59

but the phrase "what i want from conj" sounds strange. conj can do it in this case though

ghadi14:06:10

(don't use flatten, it doesn't do what you'd want in 99% of cases)

👍 3
noisesmith15:06:06

to me this looks like a classic use case for into

amirali15:06:55

thank u all. concat, into and apply all work out. but then can i use them in reduce on characters of a string?( i want to analyze characters. but i get Don't know how to create ISeq from: java.lang.Character. on following code:

(defn stack [one two]
  (if-let [oneLast (last one)]
    (if-not (and (= oneLast \() (= two \)))
      (into [] (concat one two))
      (into [] (drop-last 1 one)))
    [two]))
(defn reduceStack []
  (let [reducedStack (reduce stack  (seq string))]))
and then i use this "stack" to reduce a string.(and findout if it used the right parentheses.)

noisesmith15:06:59

(concat one two) makes no sense if two could be equal to \)

noisesmith15:06:32

you can use (conj one two) if one is a vector and two is an element for the vector

noisesmith15:06:28

also you can replace (into [] (drop-last 1 one)) with (pop one) if one is a vector

amirali15:06:07

oh... one is a char like \a \b

noisesmith15:06:26

also, the last arg to reduce is always processed via seq, you don't need to do that by hand

noisesmith15:06:03

@U014CTZJK2S how could one be a character if above you call last on it?

noisesmith15:06:22

I think you need to step back and think more clearly about the data representation here

👍 3
noisesmith15:06:14

@U014CTZJK2S a subtle and easy to miss error: you use reduce without the optional init arg, which means that on the first iteration, you get a Character as your "one", and on all further iterations you would get a Vector

noisesmith15:06:52

I think you want (reduce stack [(first string)] (rest string))

👍 4
noisesmith15:06:12

that way the data type provided to stack remains consistent

noisesmith15:06:06

also, you can simplify your code with the stack oriented functions (which work on vectors) peek (gets the last item of a vector) pop (gets the vector without the last element)

👍 3
🙏 3
amirali19:06:58

@noisesmith Yes u are right. but still i get "Don't know how to create ISeq from: java.lang.Character" from :

(defn stack [one two]
  (let [lastOne (last one)]
        (if (and (= lastOne \{) (= two \}))
          (pop one)
          (conj one two))))

(defn reduceStack []
  (let [reduced (reduce stack [(first string) (rest string)])]
    reduced))
Am I doing something wrong with string?

noisesmith19:06:28

you want (reduce stack [(first string)] (rest string)) and then the rest should just work

noisesmith19:06:46

you are using an extra arg to reduce here (one which many of us wish was mandatory...)

amirali19:06:41

@noisesmith Thank you so much. saved me hours ....

gon14:06:39

try with (into [1 2 3] [4 5])``

💯 3
sova-soars-the-sora15:06:22

is that like a latex file

noisesmith15:06:56

it's a file used to make latex bibliographies http://www.bibtex.org/

Alex Miller (Clojure team)15:06:27

source for the paper is latex so I can ask Rich if it's somtehing he might already have

Jim Newton15:06:32

A bibtex entry looks something like this:

@inproceedings{hickey2008clojure,
  title={The Clojure programming language},
  author={Hickey, Rich},
  booktitle={Proceedings of the 2008 symposium on Dynamic languages},
  pages={1},
  year={2008},
  organization={ACM}
}
It's what you need to cite one paper in another paper.

Alex Miller (Clojure team)15:06:02

so are you looking for the list of references in the paper?

dpsutton15:06:16

i think its how to cite this paper

Alex Miller (Clojure team)15:06:23

or the reference for this paper itself?

Jim Newton15:06:45

No, I'm interested in citing This new clojure paper in another paper i'm submitting to Dynamic Languages Symposium in one month.

Alex Miller (Clojure team)15:06:45

the info you need is in the footer isn't it?

Alex Miller (Clojure team)15:06:11

this is a pre-print, I don't think the proceedings have actually been published yet

Jim Newton15:06:35

Yes, I can reconstruct it. But normaly one uses the official bibtex entry provided by the author or the publisher. But yes, it's not so difficult to construct one from scratch.

Alex Miller (Clojure team)15:06:00

ACM Reference Format:Rich Hickey. 2020. A History of Clojure.Proc. ACM Program. Lang.4, HOPL, Article 71 (June 2020), 46 pages.

Jim Newton15:06:16

Is that a journal, or a conference preceedings. I think it is a journal.

Jim Newton16:06:06

ahh. good, I guessed wrong. HOPL is History of Programming Languages?

Jim Newton16:06:11

Nice article by the way.

sova-soars-the-sora15:06:34

I'm wanting to make a chatroom ... I thought about using Chord but the example project is not compiling... Sente is great but a little bit of overkill. Ideally I want: A) way to refer to each user connected and B) back-end server so I can serve static html as well

dpsutton15:06:58

some kind of websockets connection will undoubtledly be needed. sente sounds right up your alley and correct-amount-of-kill

😄 4
phronmophobic16:06:21

if you’re interested in an example. I recently made a simple web app that let’s you play a networked board game with friends. It’s uses websockets to sync data between the open tabs: https://github.com/phronmophobic/wavelength_helper/tree/networking webserver code: https://github.com/phronmophobic/wavelength_helper/blob/networking/src/wavelength_server/core.clj single file html/css/js: https://github.com/phronmophobic/wavelength_helper/blob/networking/resources/public/index.html

❤️ 8
phronmophobic16:06:17

all messages sent to the server on the websocket are broadcast to all the other users in the same “room”. there’s not a chat room, but it would be pretty straightforward to adapt for that use case

chepprey23:06:31

I've combined websockets and coreasync, which has some pubsub capabilities. I thought they were a nice combo. I also felt sente was kinda heavy for my modest needs.

Jim Newton15:06:05

Often a site which lets you download a paper, also lets you download the bibtex.

sova-soars-the-sora15:06:01

Any suggestions for extremely lightweight two-way comms with subscriptions?

Jim Newton16:06:07

I don't understand how to use set! to change the value of a global variable. Is this deprecated now? https://clojuredocs.org/clojure.core/set! What is the correct way to write a macro which registers a global resource. e.g., a top level defsomething macro ?

Jim Newton16:06:18

Here's what I tried with swap! but it doesn't work

(def supported-nontrivial-types
  "Which types are currently supported?  This list denotes the
  type names which appear as (something maybe-args), which are
  supported by RTE.  The goal is to support all those supported
  by typep, but that's not yet implemented."
  (atom #{}))

(defmacro register-type [type-name]
  `(swap! supported-nontrivial-types
         (fn [_] (conj @supported-nontrivial-types '~type-name))))

(register-type rte)
(register-type =)
(register-type member)
at (register-type rte) i get the following error
Show: Project-Only All 
  Hide: Clojure Java REPL Tooling Duplicates  (23 frames hidden)

2. Unhandled clojure.lang.Compiler$CompilerException
   Error compiling src/clojure_rte/rte.clj at (126:1)
   #:clojure.error{:phase :macro-syntax-check,
                   :line 126,
                   :column 1,
                   :source
                   "/Users/jimka/Repos/clojure-rte/src/clojure_rte/rte.clj",
                   :symbol clojure.core/fn}
             Compiler.java: 6971  clojure.lang.Compiler/checkSpecs
             Compiler.java: 6987  clojure.lang.Compiler/macroexpand1
             Compiler.java: 7092  clojure.lang.Compiler/analyzeSeq
             Compiler.java: 6789  clojure.lang.Compiler/analyze
             Compiler.java: 6745  clojure.lang.Compiler/analyze
             Compiler.java: 3888  clojure.lang.Compiler$InvokeExpr/parse
             Compiler.java: 7108  clojure.lang.Compiler/analyzeSeq
             Compiler.java: 6789  clojure.lang.Compiler/analyze
             Compiler.java: 6745  clojure.lang.Compiler/analyze
             Compiler.java: 6120  clojure.lang.Compiler$BodyExpr$Parser/parse
             Compiler.java: 5467  clojure.lang.Compiler$FnMethod/parse
             Compiler.java: 4029  clojure.lang.Compiler$FnExpr/parse
             Compiler.java: 7104  clojure.lang.Compiler/analyzeSeq
             Compiler.java: 6789  clojure.lang.Compiler/analyze
             Compiler.java: 7173  clojure.lang.Compiler/eval
             Compiler.java: 7131  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  clojure.core/eval
                  main.clj:  414  clojure.main/repl/read-eval-print/fn
                  main.clj:  414  clojure.main/repl/read-eval-print
                  main.clj:  435  clojure.main/repl/fn
                  main.clj:  435  clojure.main/repl
                  main.clj:  345  clojure.main/repl
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                regrow.clj:   20  refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   79  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   55  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  142  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  171  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  170  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  834  java.lang.Thread/run

1. Caused by clojure.lang.ExceptionInfo
   Call to clojure.core/fn did not conform to spec.
   #:clojure.spec.alpha{:problems
                        ({:path [:fn-tail :arity-1 :params],
                          :reason "Extra input",
                          :pred
                          (clojure.spec.alpha/cat

noisesmith16:06:33

deref of the thing you swap! on inside swap! is an error

noisesmith16:06:49

the value of the thing is passed to the swapping function, that's the value you need to use

noisesmith16:06:12

`(swap! supported-nontrivial-types
        conj '~type-name)

noisesmith16:06:07

also, this is symbol in / symbol out, you can just use ' in the initial call and use a function instead of a macro

noisesmith16:06:33

finally, the compilation error is because (fn [x] ...) is invalid inside ` - the transform turns x into ns/x which is an invalid binding

Jim Newton16:06:15

Ahh I've had this problem many times. what is the correct way to put an anonymous function inside a quasiquote?

noisesmith16:06:42

(fn [x#] ...)

✔️ 3
noisesmith16:06:54

but you don't want that here, for reasons already described

Jim Newton16:06:18

great that works.

Jim Newton16:06:29

so this works as well.

(defmacro register-type [type-name]
  `(swap! supported-nontrivial-types
          (fn [_#] (conj @supported-nontrivial-types '~type-name))))

Jim Newton16:06:06

but as you pointed out this is simpler:

(defmacro register-type [type-name]
  `(swap! supported-nontrivial-types
          conj '~type-name))

noisesmith16:06:07

the fn is not needed, and the deref inside swap! is a race condition

noisesmith16:06:18

not just simpler - actually correct

noisesmith16:06:31

if you need the anon-fn in the future, use the value passed in instead of calling deref inside the body:

(fn [v#] (conj v# ...))

✔️ 3
noisesmith16:06:52

usually you don't need this due to how swap! lets you use varargs

✔️ 3
noisesmith16:06:08

Used to set thread-local-bound vars, Java object instance
fields, and Java class static fields.
set! is specifically for thread local (`^:dynamic`) vars, and java interop

noisesmith16:06:28

to create a var, you can use def or intern

ghadi16:06:46

I would be cautious of registering global resources in general

💯 3
Jim Newton16:06:53

cautious in what sense?

ghadi16:06:07

like don't do it

ghadi16:06:15

e.g. if it's a database connection

ghadi16:06:19

you can use alter-var-root to alter non-dynamic vars. Again I caution against it

Jim Newton16:06:40

no, its a global definition whose value needs to change as the program loads. In the same sense that defmulti defines a global resource and defmethod updates it. Or defn defines a global function. These are not generally seen as something to avoid.

ghadi16:06:04

an example of a common pattern is the spec registry. that's an atom that gets swapped: (swap! registry assoc key some-spec)

ghadi16:06:20

(defonce registry (atom {}))

ghadi16:06:42

the defonce is there so that a namespace reload doesn't blow it away

ghadi16:06:08

rather than swapping the var itself, swap an atom that the var points to

Jim Newton16:06:14

I was trying to use atom and swap. but couldn't make it work.

ghadi16:06:50

it would be helpful if you pasted what you tried

noisesmith16:06:56

that was a macro error, described in the thread under that code

noisesmith16:06:09

but there are multiple problems before hitting the macro error

ghadi16:06:16

ok I see it

ghadi16:06:23

yeah that doesn't have to be a macro at all

ghadi16:06:58

(swap! supported-nontrivial-types conj new-thing)

noisesmith16:06:03

it doesn't need to be a macro, doesn't need an anon fn even if it was a macro, and shouldn't be derefing inside swap!

Jim Newton16:06:56

@ghadi, would you rather write a call to swap! or a (defmethod ...) ? of course defmethod doesn't have to be a macro, but it is friendlier.

ghadi16:06:19

hate to tell you but it depends

ghadi16:06:44

you need to clarify the higher level goal to choose between a simple set containing values or open dispatch (a multimethod)

ghadi16:06:31

are you associating different behavior with the different registered things? then multimethod

ghadi16:06:02

if you must declare something before using it, but all things are handled the same way, then a set is sufficient

Jim Newton16:06:24

Yes, depends on the situation. But in general the correct way to manage global resources in clojure has been unclear to me.

valerauko16:06:56

what would be such a global resource?

ghadi16:06:11

there's not a single correct way, there's only situational considerations

valerauko16:06:01

i think you might need something like mount https://github.com/tolitius/mount

☝️ 4
4
Alex Miller (Clojure team)16:06:13

the best answer is to not have a global resource, but instead to instantiate your resource when the program starts and give it to the code that needs it

Alex Miller (Clojure team)16:06:33

if it's stateful, wrap it in an atom

noisesmith16:06:49

based on the example code, I think "resource" is being used loosely here. in clojure itself, there are multiple examples of a central registry that gets appended as you develop - defmethod, defprotocol, ns, spec

noisesmith16:06:35

where the registry itself is an implementation detail, but the behavior offered is the same variety as @jimka.issy is trying to implement

noisesmith16:06:22

so a first hint might be not making the registry part of the public api, if you do need one

Jim Newton16:06:24

Yes this is what I'm calling a global resource. i.e., the global set of multi methods, or the global set of name spaces.

Jim Newton16:06:19

and yes, one reason for the macro is to hide the implementation using atom and swap!

noisesmith17:06:10

after various experiments and designs, my current heuristic is to avoid things that are not referentially transparent, whether implemented by myself or clojure, and if I can prove I need something that's spooky (eg. driven by a hidden mutable registry), I stick to the thing that's already in clojure itself unless I can prove I need a new one whose behavior clojure can't do properly

noisesmith17:06:01

I need a small amount of spookiness in order to do normal repl dev, of course. But beyond that I can keep it pretty minimal.

Jim Newton17:06:15

Does that mean you make your implementation details apparent to the user of the API? Isn't that limiting if you want to change the implementation later?

noisesmith17:06:57

if my implementation is value focused and referentially transparent, there are no details to leak

noisesmith17:06:13

but for a case like yours - you could use a multimethod and then dispatch on a symbol, where a simple false is the default method output, and true is set for anything you extend

Jim Newton17:06:21

I'm not sure what referentially transparent means in this context. normally it means you can replace a reference with the implementation. right?

noisesmith17:06:55

right - so you aren't using mutation of some other location in application logic

Jim Newton17:06:44

isn't defining a method a mutating operation?

noisesmith17:06:23

so a defmulti version of what you had above:

(defmulti registered? dispatch)
(defmethod registered? :default [_] false)
(defmethod registered? '= [_] true)

noisesmith17:06:46

right - as I said, I avoid those things, but when I need them, I prefer something already in the language

Jim Newton17:06:16

And how do I get the set of registered symbols, to iterate over them, or to print them in a diagnostic message?

noisesmith17:06:16

I picked defmulti because it looked like the set of registered things would be extended arbitrarily by client code

Jim Newton17:06:08

I guess it would use the method reflection api?

Jim Newton17:06:23

explicitly throwing away :default?

noisesmith17:06:50

that makes it uglier, I didn't realize you needed the registered items for iteration

Jim Newton17:06:02

In my case each symbol registered adds a new syntactical element to a DSL.

Jim Newton17:06:40

when the DLS is parsed, and an unregistered symbol is encountered, I print the valid symbols as part of the error message.

noisesmith17:06:17

(remove #{:default} (keys (methods m))) seems like it would do that

Jim Newton17:06:38

frankly, even though I was skeptical, its not a bad suggestion.

noisesmith17:06:08

it's a bit of a forced fit sadly - you could wrap the fact that it's a multi with a simple set of functions

Jim Newton17:06:10

in case you're interested

noisesmith17:06:34

anyway, you don't need a "reflection api"

(ins)user=> (doc methods)
-------------------------
clojure.core/methods
([multifn])
  Given a multimethod, returns a map of dispatch values -> dispatch f
ns
nil

marreman18:06:44

Hi friends! 👋 I’ve been working through the https://exercism.io/tracks/clojure using Emacs with Cider and Paredit. I struggle with the ergonomics of Clojure. For a while I had problems with wrapping a form in another form but I’m gradually getting used to slurping with Paredit and that’s ok now. A thing which hasn’t cleared up so much is how to investigate how a program works. Like evaluating some part of a bigger form in order to see what I get, or just dropping in some logging. Does anyone have experience with https://github.com/clojure/tools.trace/https://github.com/clojure/tools.trace/? I think pairing with, or just watching a seasoned Clojurist work for a while might help me discover good techniques. I’d be grateful for any advice or links to videos or other material. Thanks! :)

hindol18:06:47

If you are already using CIDER, you are in luck. https://docs.cider.mx/cider/debugging/debugger.html

hindol18:06:23

There are a bunch of other things there, apart from the debugger. I have not used those, but looks super helpful.

marreman18:06:51

Oh! There’s a bunch of stuff there! I’ll go back and read the manual!

noisesmith19:06:59

also consider that if you are coding idiomatically (using immutable data) you can use swap! or def to capture data in a function, and then use it as you like in the repl

marreman19:06:29

Ok, you mean like writing data from inside a function to an atom outside?

👍 4
noisesmith19:06:23

eg.

(def debug-data (atom []))
(add-tap (fn [x] (swap! debug-data conj x)))

(defn f [x y x]
  (tap> {:context ::f :x x :y y :z z})
  ...)
where f is some existing function

noisesmith19:06:02

there are a few corner cases where a proper debugger like in CIDER is still clearly better (when using volatile state / ephemeral objects that lose meaning outside one narrow context), but good clojure code minimizes such things, and the technique using tap> and an atom works everywhere (no matter your editor or run environment)

noisesmith19:06:37

a variant for code you don't control:

(alter-var-root #'some-lib/foo (fn [f] (fn [& args] (tap> {:context :some-lib/foo :args args}) (apply f args)))

marreman19:06:24

So for example when I want to tap some data I’d replace (existing-form) with (do (tap> a) (existing-form))

marreman19:06:09

Or tap just evaluates the third thing?

noisesmith19:06:10

pretty much - you can send whatever you want to tap> - note that defn, let, when etc. all have implicit do already

marreman19:06:33

Oh! That’s neat

marreman19:06:04

So the “variant for code you don’t control” is like wrapping a function and tapping function args?

noisesmith19:06:08

yeah - the basic template is (alter-var-root #'foo/bar (fn [f] (fn [& args] (apply f args))) - that's the no-op version, but you can fill in any other logging / inspection / alteration of args or return value as needed for debugging

marreman19:06:33

And that’s something you’d do in a REPL? Some (in-ns 'x) and then do the thing with a symbol?

noisesmith19:06:06

you don't need to change namespace - it takes a fully qualified var

noisesmith19:06:36

yes, this is something you'd set up via a repl or scratch buffer

noisesmith19:06:45

(ins)user=> (alter-var-root #'clojure.core/subs (fn [f] (fn [& args] (println "subs:" args) (apply f args))))
#object[user$eval208$fn__209$fn__210 0x1ab6718 "[email protected]"]
(ins)user=> (subs "hello" 1 3)
subs: (hello 1 3)
"el"
(cmd)user=> (subs "hello" 3)
subs: (hello 3)
"lo"

marreman19:06:18

A scratch buffer is just a place to evaluate some forms from? Can you use Emacs normal scratch buffer for this?

noisesmith19:06:27

there are gotchas (like you need to require / reload the entire namespace to get the old def back), but it is a useful trick

noisesmith19:06:07

yeah, by scratch buffer I just meant code that isn't part of your project that you can send to your repl, some people find that preferable to typing into a repl directly

noisesmith19:06:29

there's a special cider-scratch buffer iirc (I haven't used emacs in years)

marreman19:06:31

Ok, yeah I like having this at the bottom of my file

(comment
  (f 1 1)
)

noisesmith19:06:39

I'll often have a separate file, not in project source control, where I combine links to places in the project code, exploratory repl snippets, and a to-do list, with the work ticket in the file name oh, and I'll put sample data captured in the functions I'm interested in the file too

noisesmith19:06:52

in emacs org-mode is perfect for this sort of thing IMHO

marreman19:06:48

Cool! Elaborate workflow!

marreman19:06:01

What are you using now after leaving Emacs?

noisesmith19:06:49

vim - but any editor would work with this workflow (I do have plugins that run a repl in a buffer, and send a selection to that repl etc. but could copy/paste the normal way...)

marreman19:06:38

Ok! I have years of experience with vim but figured I try some Emacs with Clojure. Tried vim-fireplace in vim. Are you using some sexp editing tools as well?

noisesmith19:06:50

vim-clojure-static, vim-sexp, neoterm and fireplace in neovim I start nrepl in a neoterm terminal, use neoterm to send selections to that terminal buffer (I like this as it gives me a trace of things I tried and outputs as the repl scrollback), and then I use fireplace for :Require which just reloads the whole file, and sometimes the jump-to-def and show-docs features of fireplace

noisesmith19:06:15

I like vim-sexp because I use text-objects heavily in editing, and that maps perfectly to clojure forms

marreman19:06:42

Thank you for all your tips!

marreman20:06:23

I’ve written down most of it.

noisesmith20:06:42

I should make a blog post, but so much of what I do is just copied from talks / posts by others in the community most of the work would be in collecting and properly citing references

sova-soars-the-sora18:06:12

Is there a reason I keep seeing

java.lang.IllegalAccessException: class clojure.lang.Reflector cannot access class jdk.internal.loader.BuiltinClassLoader (in module java.base) because module java.base does not export jdk.internal.loader to unnamed module @5aa6da2

sova-soars-the-sora18:06:42

Which JDK ought I be using?

Alex Miller (Clojure team)18:06:37

Many reflective calls will be reported this way now unfortunately, even though a type hint is really needed in the Clojure code

sova-soars-the-sora19:06:06

does it make any sense to ask the upstream java people to put it back? lol

sova-soars-the-sora19:06:26

complecting at a distance

sova-soars-the-sora19:06:47

quantum enplectlement

💯 3
sova-soars-the-sora19:06:09

Missing a typehint... hmm

Alex Miller (Clojure team)19:06:12

if you use the debug flag it's usually pretty clear where the issue is

sova-soars-the-sora20:06:46

I don't know where to add that flag. Is it a jvm-opts? I'm using lein to run a project

Alex Miller (Clojure team)21:06:38

nothing special, just an example of a meta keyword

noisesmith21:06:51

(ins)user=> (meta ^:journal {})
{:journal true}

noisesmith21:06:14

various tools / libs use metadata, it's kind of a grab bag

noisesmith21:06:38

oh 😆 that example is incorrect

(ins)user=> (meta ^:journal 'foo)
nil
(ins)user=> (meta '^:journal foo)
{:journal true}

noisesmith21:06:42

someone should edit that page

Alex Miller (Clojure team)21:06:16

well the example showing it doesn't work still doesn't work so that part's not wrong :)

noisesmith21:06:43

but the example claiming it works - while irrelevant to the thing being documented - is still incorrect

noisesmith21:06:12

that's likely clearer, yes

andy.fingerhut21:06:09

page edited. 30 seconds!

💯 3
noisesmith21:06:41

haha, thanks, I didn't have login handy or I would have done it myself

Daniel Östling22:06:06

What's the common way to break long string literals to keep line width reasonable? Do people just use something like (str "long string 1" <line break here> "long string 2")?

noisesmith22:06:10

either str or format yeah - depending on what's clearer in your context

👍 3
noisesmith22:06:38

also you can put it in a file and load it with io/resource of course

iyerland22:06:38

Are there any new books out? What resources do you use to learn Clojure?

seancorfield22:06:49

Living Clojure and Getting Clojure are good books for that. Can't remember when they came out but they're relatively recent.

seancorfield22:06:57

Online, there's a version of Clojure for the Brave and True -- some people find it too quirky, and it starts out assuming you're going to learn Emacs which some people think is a bit much if you're trying to learn both Clojure and Emacs at the same time.

seancorfield22:06:29

And there's the official Getting Started material https://clojure.org/guides/getting_started

seancorfield22:06:59

That has a lot of links to books, tutorials, and so on.

iyerland22:06:18

Thanks @seancorfield ... guess I am on the right track, just got started with Living Clojure 👍

noisesmith22:06:45

be sure not to miss this (linked from the page @seancorfield shared) https://clojure.org/guides/learn/syntax

noisesmith22:06:11

it's a really good "learn how to fish" intro that relies on built in documentation and discoverability in the language itself

iyerland22:06:22

@noisesmith Thanks, will take a look at that too 👍