Fork me on GitHub
#beginners
<
2020-09-17
>
jon02:09:38

What IDE do most Clojure programmers use who also frequently work in other languages? I tried Calva with Paredit mode but it’s thrown me for a loop to switch to such a different mental model. So I’m wondering if it’s just usual to go from something like Vim style editing when working with JavaScript/Python/Ruby/C++, to a LISP-specific style like Paredit within the same day

seancorfield07:09:21

@jon920 My recommendation when learning Clojure is always to stick with the editor you already know best, as long as it has one or more Clojure integrations (which vim does have -- see #vim channel and also #conjure which works with Neovim).

seancorfield07:09:46

Most Clojure developers use Emacs, but I really wouldn't recommend trying to learn both Emacs and Clojure at the same time. I believe there is a vim-mode for Emacs (evil mode?) There's an #emacs channel if you want to get details. There's also a Spacemacs variant of Emacs that quite a few people seem to like.

seancorfield07:09:35

The next most popular is Cursive for IntelliJ -- but then you're in serious IDE land. And that is followed by VS Code and Calva.

seancorfield07:09:24

I program in a number of languages and I rely on Atom with Chlorine (after years of using Emacs).

pez08:09:05

I think the switch in model is due to the language (LISP vs non-LISP) and not to the particular editor used. Paredit is there to help you take advantage of the structural nature of LISP. I completely agree with @seancorfield that if your usual editor of choice has decent Clojure support, then it is best to stick to it, at least until Clojure sits comfortably under your belt. So if vim is your usual goto, you are in luck. If VS Code with VIM Extension is your usual goto, then it can be a bit of setup needed to make Calva and VIM play nicely together, please check https://calva.io/vim/ out for some starter hints. Please feel very welcome to join #calva for assistance from other Calva + VIM users.

kimim12:09:32

Emacs is the best choice.

chucklehead03:09:15

You’re not alone. I switch between Calva and Emacs. I still inevitably do some line-based cut/paste instead of slurp/barf when trying to reorder a binding block or something and 90% of the time it’s fine, but every now and then I fail to notice I grabbed a delimiter or a #_ and all hell breaks loose.

jon03:09:54

I’ve never used Emacs, is that like using Sublime Text where you navigate with the arrow keys?

chucklehead03:09:01

You can use a mouse but it’s definitely heavily biased towards keyboard navigation.

practicalli07:09:49

@jon920 Most Clojure developers use Emacs (according to the yearly Clojure survey). There are a number of editors/ide's that support Clojure, so as Sean recommend you should ideally use an editor you are familiar with when starting Clojure. https://practicalli.github.io/clojure/clojure-editors/ However, I did take the opportunity to learn Emacs again when I started, especially as Cider is such a great tool for Clojure development. Developing Clojure is different to other languages and its highly recommend you take a repl driven approach https://practicalli.github.io/clojure/repl-driven-devlopment.html Also, getting comfortable with structural editing makes Clojure coding more effective https://practicalli.github.io/spacemacs/structural-editing/ These concepts are not fully realised or not available in other languages, so their will be some discomfort when switching between languages, especially if doing so several times a day. And of course regular task switching between different applications in different languages, typically with a different design approach, is never got to be as effective as focusing one one language and one app.

seancorfield08:09:31

I'll definitely +100 that comment about REPL driven.

💯 1
Jim Newton08:09:30

I'm still confused about print-method ... When I evaluating the following at the repl, I don't get the results I expect.

clojure-rte.core> (defrecord Foo [x])
clojure_rte.core.Foo
clojure-rte.core> (defmethod print-method 'Foo [v w]  (.write w (format "#<Foo x=%s>" (:x v))))
#multifn[print-method 0x23771d94]
clojure-rte.core> (map->Foo {:x 100})
{:x 100}
clojure-rte.core> (pprint (map->Foo {:x 100}))
{:x 100}
nil
clojure-rte.core> (cl-format false "~a" (map->Foo {:x 100}))
"#clojure_rte.core.Foo{:x 100}"

Jim Newton08:09:43

I expect it to print as #<Foo x=100>

Jim Newton08:09:59

I have verified that I defined the method in the correct namespace. A call to (methods print-method) returns a huge map which contains among other things: Foo #function[clojure-rte.core/eval14785/fn--14786],

Jim Newton08:09:15

even prn prints something strange

clojure-rte.core> (prn (map->Foo {:x 100}))
#clojure_rte.core.Foo{:x 100}
nil
clojure-rte.core> 

Jim Newton08:09:23

could it be that my method is on Foo in the wrong namespace?

Jim Newton08:09:25

hmm. Looks like the system is not calling my method. (get-method print-method (map->Foo {:x 100})) is returning #function[clojure.core/fn--7252] rather than #function[clojure-rte.core/eval14785/fn--14786]

Jim Newton08:09:30

what might be causing this?

Lennart Buit08:09:17

Its the quote in 'Foo, remove that and it works for me

👍 1
Jim Newton08:09:41

cool thanks.

Jim Newton08:09:28

now I get the following:

clojure-rte.core> (prn (map->Foo {:x 100}))
#<Foo x=100>
nil
clojure-rte.core> 

Jim Newton08:09:16

However, still returns #function[clojure.core/fn--7252] ... perhaps that function has additional code in it which dispatches on records?

Jim Newton08:09:59

I expect it to print as #<Foo x=100>

Jeff Evans15:09:13

This blog post suggests that conj should be used to prepend to a list: https://medium.com/@greg_63957/conj-cons-concat-oh-my-1398a2981eab However, this post suggests that actually cons is a much faster way (which my own testing also verifies): https://forum.freecodecamp.org/t/how-to-use-clojure-lists-the-list-data-structure/18417 Is there a definitive answer somewhere? The data structures page doesn’t discuss this: https://clojure.org/reference/data_structures#Lists I tried reading through core.cljc but couldn’t quite make sense of the handoff to Java to find where they’re implemented.

andy.fingerhut15:09:06

If you want to look for underlying Java implementation details for Clojure on the JVM, then you should look in core.clj , not core.cljc. You can also do (source cons) and (source conj) in a REPL session to see their source code.

andy.fingerhut15:09:44

There are Java interop calls with the Java method names there, and the Java package clojure.lang.RT that they are defined in, which can be found in a source file RT.java

andy.fingerhut15:09:29

I do not have an authoritative answer for you, but some comments that I don't know how useful they will be.

andy.fingerhut15:09:56

cons returns a seq, meaning 'some type, but no promised specific type, that implements the Clojure seq interface', sometimes called a sequence.

andy.fingerhut16:09:13

conj returns a collection that is "the same type" as the collection you give it. I put "the same type" in scare quotes because the Java class of the returned collection might be a different concrete Java class, but even then, it should be a Java class that is "the same kind of collection", i.e. a list if the input collection is a list, a map if the input collection is a map, vector if vector, etc.

andy.fingerhut16:09:32

In contexts where you do not care if you have a list, and any sequence is good enough, then cons is a reasonable choice. I do not know off the top of my head any places where it is important to have a list rather than 'some kind of sequence'.

andy.fingerhut16:09:06

I guess one difference is that lists implement count in O(1) time, because the JVM object for a list has a precalculated field storing the count, but many concrete classes that implement the sequence interface do not have such a precalculated count field, so calculating their length takes O(n) time.

pithyless16:09:10

There is an old post on SO that explains some of the technicalities of cons vs conj: https://stackoverflow.com/a/12390331

Jeff Evans16:09:47

thanks to both of you, this is all extremely helpful

andy.fingerhut16:09:55

@U05476190 Good points there that conj must realize its 'collection' parameter, but cons leaves it unrealized. That definitely sounds like a consequence of the "cons for sequence, conj for collection" distinction.

andy.fingerhut16:09:44

or at least they are strongly correlated -- not sure which way cause and effect went in the design of these things 🙂

vncz16:09:45

Does anybody know anything to parse nested query strings in Pedestal? It is currently able to automatically turn qs in maps (`?q=1&b=2` => {:q 1 :b 2} ), but I would need something a little bit more elaborate, such as ?foo[bar]=baz => { :foo {:bar "baz" } }

stopa20:09:28

Hey team, random q: I notice that if I call a symbol as a function, it returns the last element it was called with:

('foo 1 2) ;; => 2 
What is the reason that symbols do this? Was a bit surprised that is what happened

dpritchett20:09:04

Not sure but

dpritchett20:09:35

user=> (doc quote)
-------------------------
quote
  (quote form)
Special Form
  Yields the unevaluated form.

  Please see 
  Yields the unevaluated form.
nil
user=> ('a 1 2)
2

👍 1
dpritchett20:09:19

user=> ((quote a) 1 2)
2

1
alexmiller20:09:44

symbols and keywords are both also functions that will look themselves up if given an associative collection

❤️ 1
alexmiller20:09:59

so same signature as get (which also has a second "not-found" arg)

❤️ 3
stopa20:09:27

Bam — Makes good sense! Thanks for jumping in team

alexmiller20:09:30

so you can do ('a {'b 2} 1)

❤️ 1
alexmiller20:09:59

very common with keywords, much less common with symbols

alexmiller20:09:13

and doubly confusing here b/c 1 is not an associative coll

alexmiller20:09:40

whether that should throw is a matter of long long debate

😆 1
dpritchett20:09:05

is there a clojure command that would fully expand a statement like ('a 1 b) to de-sugar everything and maybe explain wha the low-level executed expression was?

alexmiller20:09:28

well, there's no sugar - symbols are directly invokable

Joel20:09:42

before i use (replace...) how do i ensure that all the values in the collection are going to be replaced?

Joel20:09:00

that is, each value is a key in the map?

Matthew Cheney21:09:04

You could check that all the values are in the collection before executing replace. A function like this might help:

(defn all-keys-in-coll
  "Returns true if all keys k are in coll c."
  [k c]
  (reduce #(and %1 (contains? c %2)) (concat [true] k)))

seancorfield21:09:32

(= (set ks) (set (keys coll))) would be simpler, assuming ks is your collection of keys and coll is the hash map you want to check.

👍 2
seancorfield21:09:46

And if you stick with reduce, it's better to use the arity with the init and avoid concat: (reduce #(and %1 (contains? c %2)) true k)

Joel21:09:01

my other thought was along the lines of do the replace and ensure the values were all different after.

Test This21:09:32

How to compile clojure app as an uberjar when using clojure cli tools and deps.edn? I am hoping to use deps.edn and clojure cli tools for the project. Can someone please point me to an easy way to create a AOT compiled jar file that I can execute using java? What steps do I need to follow. I am trying with a simple app and getting errors like those shown below. I will post the code and errors, but before I do that, I thought I would ask what is the recommended way to do this and try that again with my application. My main goal is to get the application in a format that can be deployed in production. I am assuming that creating a jar/uberjar is the way to do that. I want to know how to get there when not using leiningen. I should add that I am able to run the application using clj -m coffeeapp without problems when I remove the (:genclass) line from the namespace. Syntax error macroexpanding clojure.core/ns at (coffeeapp.clj:1:1). :genclass - failed: #{:refer-clojure} at: [:ns-clauses :refer-clojure :clause] spec: :clojure.core.specs.alpha/ns-refer-clojure :genclass - failed: #{:require} at: [:ns-clauses :require :clause] spec: :clojure.core.specs.alpha/ns-require :genclass - failed: #{:import} at: [:ns-clauses :import :clause] spec: :clojure.core.specs.alpha/ns-import :genclass - failed: #{:use} at: [:ns-clauses :use :clause] spec: :clojure.core.specs.alpha/ns-use :genclass - failed: #{:refer} at: [:ns-clauses :refer :clause] spec: :clojure.core.specs.alpha/ns-refer :genclass - failed: #{:load} at: [:ns-clauses :load :clause] spec: :clojure.core.specs.alpha/ns-load :genclass - failed: #{:gen-class} at: [:ns-clauses :gen-class :clause] spec: :clojure.core.specs.alpha/ns-gen-class

hiredman21:09:41

That error doesn't having anything to do with aot compiling, you have a malformed ns expression somewhere

hiredman21:09:38

You will get the same error just trying to load the file in your repl (assuming your are using the same version of clojure)

seancorfield21:09:34

@curiouslearn It's (:gen-class) not (:genclass)

seancorfield21:09:10

And you probably want to look at https://github.com/seancorfield/depstar as the easiest way to build uberjars with the CLI.

seancorfield21:09:28

You could add an alias :uberjar to your project that had depstar in the :extra-deps and then :main-opts ["-m" "hf.depstar.uberjar" "coffeeapp.jar" "-C" "-m" "coffeeapp"]

seancorfield21:09:31

Then clojure -A:uberjar should compile coffeeapp (your main ns that has (:gen-class) in it) and build coffeeapp.jar which you could run with java -jar coffeeapp.jar

Test This21:09:46

@seancorfield Thank you very much. I will try that out. I did not know about that.

Joel21:09:06

i tend to write function that transform one item, so much of the time though i realize i want elements to "go away" if not appropos, so i have to put (filter some?) in my calling function... so now, i'm swinging toward having functions that return multiple and filter out the unwanted ones in that function. I felt like in Elmlang as well this was always nagging, but don't know clean way to handle.

Joel21:09:57

i suppose i'm being lazy and should write separate filters functions for everything 😞

alexmiller21:09:02

does keep help?

az22:09:40

Hi all, wondering if anyone can give me tips on dealing with circular dep errors? I find that I am having to quote quite a bit to get around this. Is this expected in most projects or am I missing some key techniques?

dpritchett22:09:17

Might be a code layout “smell”

dpritchett22:09:54

If two libs require each other maybe there’s a third lib itching to be extracted out of them

✔️ 1
hiredman22:09:16

Quote what? Quoting effects evaluation, not namespace dependencies

phronmophobic22:09:11

circular deps seems to be a common problem, but I'm not sure how programs fall into that predicament. typically, you set up all the pure functions needed to get the work done. All of the pure functions are generally independent of each other (which is why pure functions make great building blocks). then you have one namespace that can consume all the necessary namespaces and put everything together. a library like https://github.com/stuartsierra/component may help, but isn't necessary.

hiredman22:09:15

a common way circular dependency errors arise is when you define some kind of interface like thing (defmulti, defprotocol, etc), people often make the mistake of putting implementations of the interface in the same file, or trying to making on the implementation namespaces dependencies of the interface namespace

hiredman22:09:12

so that would be my guess as how you are getting yourself tangled, but a lot of that is based on supposition and doesn't explain whatever thing you are doing with quoting that somehow solves this problem for you

Test This22:09:17

@seancorfield Thank you so much. I was not sure how to get the :main-opts working. However, your instructions on depstar site worked great. If it were not for you, I think I would have given up on Clojure. The language is fun, and I can learn it without problems. However, the tooling is the hardest of all languages I have used (quite likely because I don't know Java). At least, it is great to know that the community is helpful to beginners.

seancorfield22:09:28

@curiouslearn If Clojure tooling is the hardest you've encountered, I can only say you've been very lucky 🙂 Unfortunately, there are a lot of languages out there with much more complex and sharp-edged tooling!

seancorfield22:09:57

(as you say, having not experienced Java probably sets your tooling expectations higher)

az22:09:37

Thank you for all the feedback. I’m in the following situation with Fulcro: 1. transacting from the UI 2. using merge-component from the mutation which I believe requires a the UI class to merge I know this may be something I’m misunderstanding with Fulcro, but I thought I would start here in general relating to strategies for avoiding this situation before potentially asking a silly question in there. I know I should probably not be working with Fulcro this early in the game. Right now I just syntax quote the (comp/transact! [(this add-task...) so that I can (merge/merge-component! app Task...)

dpritchett22:09:39

Most hosted languages are super hard to debug without a few years’ full time dev experience IME

Test This22:09:05

Yeah, I think it is because of me not knowing Java. I have not used many other languages; only ones I have used are: Python, Julia, Crystal, Go, and on frontend Javascript and Elm. I guess Python and Julia don't have AOT compilation, so they don't count. Crystal and Go do, and they were much easier to create an executable. To all: please don't consider this as a criticism. Just a beginner's viewpoint, and want to let you know that I am grateful for your help.

1
seancorfield22:09:35

@curiouslearn Well, there's also a different set of goals and overall approach in Clojure. There's quite a big push toward run-from-source, since Clojure itself compiles code on-demand. For years at work, we just ran all our production Clojure code from source. Then we started packaging it into JAR files and still ran it from source (`java -cp the.jar clojure.main -m entry.point` calls entry.point/-main without needing anything except clojure.main being compiled -- which it is).

seancorfield22:09:35

We only switched to AOT fairly recently (when I added it to depstar in fact), to improve startup time -- and startup time was the only reason we did this, so auto-deployed services would come back online in a few seconds rather than a minute or two.

seancorfield23:09:56

The Clojure CLI / deps.edn treats source code in git repos as a first class citizen, for example, so you can depend on a project in a git source repo without ever needing it packaged and released to Maven/Clojars, thus avoiding a lot of the Java ecosystem that we otherwise interact with.

seancorfield23:09:12

You can have a REPL running in a live, production process and evaluate updated code into it while it is running (we do this at work for an internal-facing project that runs as a single server instance and we don't want downtime for small fixes). You can't do that with most languages.

😍 1
seancorfield23:09:14

Locally, I develop via the REPL into a running server process all the time. I don't even need to save changes in order to test them: just eval changes into the REPL and try out the new behavior. I can run my unit tests inside the REPL too, so I don't need to leave my editor for that. No "watchers", no "reloading".

Test This23:09:04

@seancorfield Yes. These are some great advantages that I didn't know about. I thought that if it is not AOT compiled, it will run slow. Second, I wanted it AOT compiled so that I could easily deploy it, by just starting the app by running the jar and then using a reverse proxy in front of it. From your example, it appears one can do that even without AOT compiling. Can something like java -cp the.jar clojure.main -m entry.point be put in systemd file and expect it to run well? Thank you.

seancorfield23:09:34

Yes, any command should work. Even clojure -m entry.point assuming you have the CLI installed on the server and you start it in the project directory. That's basically what we did before we started packaging projects as JAR files.

seancorfield23:09:56

And we still have quite a few commands we run in production with java -cp the.jar clojure.main -m entry.point

Test This23:09:14

Thank you very much. It is great to know that even clojure -m entry.point is production ready. What is the difference between clojure -m entry.point and java -cp the.jar clojure.main -m entry.point? Thank you once again for the explanations.