Fork me on GitHub
#beginners
<
2023-01-31
>
Chitradeep Dutta Roy19:01:52

Hi I am new to clojure. I use emacs with cider. I am trying to setup a project for testing libraries and trying out stuff. I came to know about experimental add-libs in clojure.tools.deps but having trouble making it work with cider. I am unsure how exactly should I use (:require) so that when I launch cider nRepl in emacs it automatically brings add-libsin scope, so that I can use (comment ...) blocks later to hotload other libraries conveniently. Appreciate any help, thanks.

seancorfield19:01:53

Have you added the alias to your user-level deps.edn?

Alex Miller (Clojure team)19:01:36

keep in mind that the add-libs branch is possibly in conflict with the recent versions of the Clojure CLI

Alex Miller (Clojure team)19:01:19

but I am hard at work to make this a Real Thing (once I get past all the side quests :)

👍 4
clojure-spin 2
seancorfield19:01:25

I'm still using tools.deps.alpha with add-lib3 and haven't run into problems -- yet! -- but I am also explicitly loading both tools.deps and tools.deps.alpha to do that.

Alex Miller (Clojure team)19:01:41

yes, that would be necessary

Alex Miller (Clojure team)19:01:51

but good to know that works

Chitradeep Dutta Roy19:01:46

No I am not using the alias. I was putting this directly in deps like

:deps {org.clojure/tools.deps {:git/sha "8f8fc2571e721301b6d52e191129248355cb8c5a"
                                :git/url ""}}

Chitradeep Dutta Roy19:01:41

and then I am simply launching cider-jack-in-clj from the directory with deps.edn

Alex Miller (Clojure team)19:01:24

it's probably best to make your own alias, not hijack :deps. and then of course, you actually have to include the alias if you want to use it

seancorfield19:01:20

The add-lib3 branch still uses the old (alpha) names. You'll need what I linked to above. But I will also caution that you that this is experimental stuff and may be broken -- I use it a lot because I don't mind stuff breaking and I'm used to debugging problems...

👍 2
Chitradeep Dutta Roy19:01:39

Yeah I read that warning everywhere, but being able to hotload is just so convenient when learning and experimenting with new stuff.

Chitradeep Dutta Roy19:01:34

I have done some common-lisp coding, am so used to ql:quickload or asdf:load-system .

Chitradeep Dutta Roy19:01:15

so If I create an alias how to instruct cider repl to load that alias

solf19:01:37

Aliases are specified before you have access to a repl, by setting the emacs variable cider-clojure-cli-aliases. But I think your original question isn’t really cider-dependant. If you have the deps directly without any alias and just want to see it work, have you tried something like this?

(require '[clojure.tools.deps.alpha.repl :refer [add-libs]])
  (add-libs '{midje/midje {:mvn/version "1.10.3"}})

Chitradeep Dutta Roy19:01:35

Yes you are right I am trying exactly that, the issue is that I am needing to write the first require in the repl, but I was hoping there would be an easy way to have add-libs pulled into scope when the repl loads by default.

Chitradeep Dutta Roy19:01:38

maybe my question is when cider loads the repl for a project is there a way to execute a script which brings some symbols into the default namespace.

solf19:01:22

There is, but I haven’t used it personally. You can try setting the emacs variable cider-repl-init-code

solf19:01:36

Its definition:

(defcustom cider-repl-init-code (list (cdr (assoc 'clj cider-repl-require-repl-utils-code)))
  "Clojure code to evaluate when starting a REPL.
Will be evaluated with bindings for set!-able vars in place."
  :type '(list string)
  :package-version '(cider . "0.21.0"))

Chitradeep Dutta Roy19:01:41

Thanks, I will try it and update .

solf19:01:15

For example, these are its default values:

(defvar cider-repl-require-repl-utils-code
  '((clj . "(clojure.core/apply clojure.core/require clojure.main/repl-requires)")
    (cljs . "(require '[cljs.repl :refer [apropos dir doc find-doc print-doc pst source]])")))

solf19:01:51

There’s also #C099W16KZ and #C0617A8PQ where you might get more help

practicalli-johnny03:02:17

I recommend 1. adding an alias to include add-libs, 2. add a .dir-locals.el file which Cider uses to start a REPL (or use the alias when starting a REPL on the command line), 3. create a dev/user.clj to load add-libs at repl startup or just use add-libs via rich comment forms https://practical.li/clojure/clojure-cli/projects/hotload-in-project/ has examples of using add-libs with projects https://practical.li/spacemacs/clojure-development/project-configuration/ - has examples of setting up a .dir-locals.el configuration for Emacs which I find the most convienient way to add an alias like add-libs https://practical.li/clojure/clojure-cli/repl-startup/ - shows how to set up a custom user namespace that can be used to automatically load add-libs on repl startup, with an example including add-libs

👍 2
Chitradeep Dutta Roy14:02:35

Thanks @U05254DQM, your suggestion worked great.

👍 2
mister_m20:01:21

I'd like to define an exception - I have a ns definition with the name of my exception in the name slot and with (:gen-class :extends java.lang.Exception) in the attr map. I realize this needs to be compiled before I can use it in a catch. How is gen-class typically handled? Is this something I'd add to something within my deps.edn and then compile through clj? Or is this a manual repl step with the compile function?

mister_m20:01:36

Looks like this is what I am looking for https://clojure.org/reference/compilation

ghadi20:01:52

for AWS Step Functions?

mister_m20:01:50

No just for chaining exceptions

ghadi20:01:36

say more?

ghadi20:01:17

(ex-info "blah blah" {} chained-cause)

Alex Miller (Clojure team)20:01:32

all Java exceptions support chaining, so it's definitely way easier to use something that exists (like RuntimeException) or the Clojure friendly ex-info instead

👍 2
mister_m20:01:55

RuntimeException should work for this context

Ivan Quirino22:01:25

Hey guys, I'm new to Clojure. I am doing some hacker rank exercises and I wrote the following code, which I know is using def to set some sort of mutable state:

(def npoints (Integer/parseInt (read-line)))

(def pairs [])

(dotimes [i npoints]
    (let [inputLine (read-line)
          numS (clojure.string/split inputLine #" ")
          pair (for [n numS] (Integer/parseInt n))]
            (def pairs (conj pairs pair))))
            
(println pairs)
What are the dangers of this code? Should I use an atom instead? Once I read all the lines, I will not "update" the pairs vector anymore, and I'll calculate something with that data.

phronmophobic23:01:26

if you are going to have mutable state, then an atom is usually the way to go. However, you can write this example without using an atom, def, or other mutable containers. I would recommend trying to write this example in a more functional style using reduce. If you're familiar with recursive styles, you can also try writing it with loop and recur .

Ivan Quirino23:01:15

the code above is just the part which I read the inputs from stdin, the problem itselft can be done functionally

phronmophobic23:01:02

I see some parsing and grouping as well. You can generate a list of strings from standard in with:

(require '[ :as io])
(def lines (line-seq (io/reader *in*)))
It's not perfect since it doesn't handle cleanup, but it's unlikely to matter unless your program runs for long periods of time or is analyzing extremely large inputs.

phronmophobic23:01:05

reduce and loop are also appropriate for dealing with I/O more functionally (rather than dotimes, doseq, def, etc)

Ivan Quirino23:01:41

thanks man

👍 2
phronmophobic23:01:04

learning reduce and loop will help cover fundamental ideas for functional programming, but they aren't necessarily easy to get "used to" (at least they weren't for me).

didibus02:02:05

line-seq already puts each line into a sequence for you, so it's much better to use that directly like recommended. If you wanted to do it yourself, you're better off first reading each line, and then processing them, so that you separate impure IO from pure logic, but it should still be done functionally:

(def lines
  (loop [lines [] line (read-line)]
    (if line
      (recur (conj lines line)
             (read-line))
      lines)))
You can put that at the start for example, so lines will now contain all lines read from the input. This is just to show you, but line-seq would be much simpler:
(def lines
  (line-seq (io/reader *in*)))
@U7RJTCH6J I actually don't think you need to clean up the reader her, because closing it would close the input stream, and *in* isn't meant to closed.

quoll15:02:47

Also, in real code, re-def’ing a symbol over and over has a performance cost. I’m processing text files that are multiple GB, so this would definitely show up. It’s really not for that. It’s also unsafe for threads, as you have a global value that you’re updating, and while global variables are sometimes necessary, they are undesirable in most languages, even unsafe languages like C. Another thing about the example code is that it’s clearly for the REPL, in that it’s stateful with the read-line calls. For this reason, it’s often better to break up some of the phases and compose them. Admittedly, there are caveats around that (e.g. processing files means either: a) loading it all into memory and passing it off; b) creating a lazy seq, but then the input stream MUST be kept open until processing is finished; or c) processing a line at a time, as is shown in the example)