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?

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 [])))

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

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.

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.

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.

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

mruzekw05:06:01

read-string and eval

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

🤔 Neat. I will take a look. Thank you guys.

mruzekw05:06:55

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

valerauko06:06:35

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

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. :)

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

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)

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

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?

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 %]

valerauko15:06:11

figured it out so made an example out of it too https://github.com/valerauko/checkbox-tree-example

SoV413:06:12

Morning All 😃

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

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]

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)

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

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))

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)

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])``

alexmiller15:06:25

what is that?

SoV415:06:22

is that like a latex file

noisesmith15:06:56

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

alexmiller15: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.

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.

alexmiller15: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

alexmiller15: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.

alexmiller15:06:45

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

alexmiller15: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.

alexmiller15: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.

alexmiller16:06:31

conference proceedings

Jim Newton16:06:06

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

Jim Newton16:06:11

Nice article by the way.

SoV415: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

SoV415: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

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

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.

SoV415:06:01

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

SoV415: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: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#] ...)

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# ...))

noisesmith16:06:52

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

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

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

alexmiller16: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

alexmiller16: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

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! :)

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?

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

SoV418: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

SoV418:06:42

Which JDK ought I be using?

alexmiller18:06:44

Any jdk will work

alexmiller18:06:37

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

SoV419:06:06

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

SoV419:06:26

complecting at a distance

SoV419:06:47

quantum enplectlement

SoV419:06:09

Missing a typehint... hmm

alexmiller19:06:12

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

alexmiller19:06:31

--illegal-access=debug

SoV420:06:46

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

alexmiller21: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

alexmiller21: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

alexmiller21:06:02

could just use a []

noisesmith21:06:12

that's likely clearer, yes

noisesmith21:06:12

that's likely clearer, yes

andy.fingerhut21:06:09

page edited. 30 seconds!

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

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 :+1:

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 :+1: