Fork me on GitHub
#clojure
<
2023-07-29
>
Dallas Surewood04:07:28

So I'm usually bootstrapping projects that use deps.edn. Today, I tried making one just from scratch and I'm confused how I get clojure to actually look at deps.edn. It doesn't seem to recognize the file even though I'm running it in the same folder.

{:paths ["src/main"]
 :deps {metosin/reitit {:mvn/version "0.5.18"}}
 :dev {:extra-deps {djblue/portal {:mvn/version "0.34.2"}}}}

hiredman04:07:59

Missing a level for :aliases

Dallas Surewood04:07:34

Thanks, fixed that. I only put the aliases in because my path wasn't working.

{:paths ["src/main"]}
Got a file in src/main/server.clj and none of it seems to be read when I start clojure

Dallas Surewood04:07:53

In fact, even (+ 1 2) doesn't work in the file

(ns main.server)

(+ 1 2) => Syntax error

(defn do-something []
  (+ 25 256))

(do-something)

hiredman04:07:31

"src" is likely the correct path not src/main

hiredman04:07:46

What did the syntax error say?

Dallas Surewood04:07:40

In calva, I was evaluating this file and got

; Syntax error compiling at (src/main/server.clj:3:1).
; Unable to resolve symbol: + in this context
In regular clojure, I can do (+ 1 2) but I none of the stuff in the namespace is defined

Dallas Surewood04:07:22

Tried with {:paths ["src"]}, didn't work

hiredman04:07:32

That is because you are getting an error loading the namespace

hiredman04:07:21

And then not seeing it for some reason, and then when you switch the repl to that namespace, the namespace hasn't been setup to refer stuff from clojure.core

hiredman04:07:00

If you replaced + with clojure.core/+ it would work because a fully qualified name doesn't need the refers

hiredman04:07:21

How are you loading main.server?

Dallas Surewood04:07:08

Does deps not load it when it's on the path?

seancorfield04:07:02

Paths just specifies where to look for code. require is what loads a Clojure namespace.

seancorfield04:07:27

If you're seeing + not defined, you probably did in-ns before you require'd the ns.

seancorfield04:07:23

In src/main/server.clj, what is your ns form?

Dallas Surewood05:07:08

(ns main.server)

seancorfield05:07:32

OK, so :paths ["src"] is definitely what you want instead of "src/main" -- because namespace "paths" are relative to the classpath: src is on the classpath, so src/main/server.clj maps to main.server relative to the classpath.

Dallas Surewood05:07:02

Alright fixed that. Still the same issue

seancorfield05:07:19

Did you restart your REPL after updating deps.edn?

Dallas Surewood05:07:40

Yes. So it sounds like there's a process that my bootstrapped apps do that I'm not doing here.

seancorfield05:07:57

Also, how exactly are you starting your REPL and what exact steps are you taking in Calva when evaluating code?

seancorfield05:07:11

There definitely seems to be something fairly fundamental you're missing...

Dallas Surewood05:07:43

I'm just doing jack in with deps.edn and then evaluating the (+ 1 2) in the file. In a regular project this would switch namespace and evaluate it

seancorfield05:07:45

(perhaps if this project is up on GitHub, we can take a look and provide more specific feedback?)

seancorfield05:07:05

Are you at least evaluating the ns form in the file, before trying to evaluate other forms that follow it?

Dallas Surewood05:07:27

When I do that, I am able to evaluate (+ 1 2). I'm wondering why it's not just loading it automatically like say a luminus app does when it's launched. There doesn't seem to be anything in dpes my other apps would be doing.

Dallas Surewood05:07:34

I can upload a repo

seancorfield05:07:39

(I'll note that posting questions in #C03S1KBA2 tends to make people assume a lot of basic working knowledge with Clojure and its tooling -- posting those same questions in #C053AK3F9 will tend to encourage answers that make no assumptions about what you know and what you did)

seancorfield05:07:29

I don't understand what you mean about "loading it automatically". Just starting a REPL doesn't load anything. Requiring namespaces is how code is loaded and compiled.

Dallas Surewood05:07:22

Yes, I had assumed before :paths would do that, but it sounds like there's something in other project that is requiring all the namespaces automatically.

seancorfield05:07:46

I suspect you're seeing behavior in your Luminus app that you think is "default" but in fact leans heavily on user.clj being loaded and executed in that project -- and you just don't realize it.

Dallas Surewood05:07:41

Let me take a look

seancorfield05:07:46

(I personally think user.clj is "evil" and I see it trip a lot of people up -- I prefer explicit actions over implicit ones so I avoid user.clj and encourage others to do the same and therefore learn the first principles without the "magic")

💯 2
seancorfield05:07:41

Luminus is a huge collection of libraries and configuration and it glosses over a lot of basic knowledge... I think it's a terrible way for beginners to learn about Clojure...

Dallas Surewood05:07:58

Despite this question, I have done a lot with my user.clj and I'm pretty familiar with how it uses Integrant and other components, but I have never had to think about this particular thing. I see user.clj requiring a lot, but I'm not seeing anything that, for instance, goes into the user namespace and evaluates the file.

Dallas Surewood05:07:42

Or is it just because the default ns clojure opens is user?

Dallas Surewood05:07:21

Okay yes, that is it

seancorfield05:07:21

When Clojure starts up, if user.clj is on the classpath, it is loaded (I think that was a terrible idea), which means all the code in that file is executed. But user.clj should never be on your default classpath -- with deps.edn, it should only ever be brought in via an alias (like :dev {:extra-paths ["dev"]} so that dev/user.clj is loaded). But I still say it's a really idea.

Dallas Surewood05:07:59

Oh yes, in the case of this project, that's what's happening. It's only there for a dev alias.

Dallas Surewood05:07:22

So that explains why that works in this project

seancorfield05:07:23

You can have a :dev {:extra-paths ["dev"]} alias and have, say, repl.clj in there and then open that file and eval it (or eval (require 'repl) from another file).

seancorfield05:07:06

You can jack-in and specify additional aliases to include in Calva so, yeah, if you didn't jack-in with :dev then it wouldn't be loaded.

seancorfield05:07:00

(again tho', if you use something other that user.clj you can still open and eval the whole file -- even if it isn't on your classpath -- once you've jacked in)

Dallas Surewood05:07:27

And I'm assuming shadow-cljs watch is also automatically evaluating function definitions, and that's why luminus projects can also read those files without evaluating the file first?

seancorfield05:07:41

No idea. I don't use Shadow. And I don't use Luminus (see my comment above).

seancorfield05:07:32

But if you don't know what the command does, ask specific questions in #C6N245JGG and #C077KDE3A etc to make sure you understand what "magic" is happening behind the scenes.

Dallas Surewood05:07:07

Sure. And to clarify, if I can evaluate files that aren't in the classpath, does the classpath do anything? is it just allowing other files to require it?

seancorfield05:07:22

This is why I encourage beginners to always start from scratch and avoid these complex templates etc.

seancorfield05:07:13

The classpath determines what code is available for the program being executed. The fact that you can evaluate additional code into a running REPL is orthogonal to that.

Dallas Surewood05:07:49

I mean I went in with Brave clojure before I ever touched luminus, like everyone recommended. But that doesn't mean I ever came across something that explained the classpath well. In fact, multiple things described it as being what loads code

seancorfield05:07:37

Evaluating a file into a running REPL is the equivalent of evaluating each of its forms, one after the other, into the REPL. The classpath determines what is available when the program starts up -- but you can add new namespaces dynamically at runtime.

seancorfield05:07:33

Well, the classpath doesn't load anything. It just tells Java (under the hood) where to look for classes and "resources" -- and that's what Clojure does when you require a namespace: it says "What resource should that namespace be defined in and can I find it on the classpath?"

Dallas Surewood05:07:58

Alright I think I get it. Can require things in the classpath only, but nothings automatically loaded. if user is requiring another namespace, I am able to see defined functions in that namespace purely because user needed to be evaluated, so those namespaces needed to be as well.

seancorfield05:07:41

Maybe this article I wrote five years ago might help? https://corfield.org/blog/2018/04/18/all-the-paths/

Dallas Surewood05:07:56

Thank you, I'll read that

practicalli-johnny07:07:14

@U042LKM3WCW the https://practical.li/clojure/ online book covers a lot of the Clojure CLI tool workflow, with lots of examples

joakimen10:07:47

How do you guys navigate your Lisp source code? I've been looking to get more into Paredit lately. I find that whenever I go anywhere, while it gives great precision, it requires so many commands. Take for example the two s-expressions in Clojure

(defn foo []
  (println "foo"))

(defn bar []
  (println "bar"))
Let's say my cursor is resting after the enclosing " in "foo", and I want to get to the same place in the bar function. In order to get there using paredit, I would have to do the following: • 2x paredit-forward-up to get out of the first s-exp (foo) • 1x paredit-forward-down to get into the next s-exp (bar) • 3x paredit-forward to get to the function body (`(println "bar")`) • 1x paredit-forward-down to get into the function body • 2x paredit-forward to get to after the "bar" in the function body That is 9 commands, just to get from one extremely small (and contrived) s-expression, to another extremely small s-expression. Usually, the expressions are WAY deeper, and requires a magnitude of more shortcuts and mental overhead to navigate (for me). Is this even what you're supposed to do with Paredit, or is Paredit-navigation meant to be used together with arrow keys? I have used vi-binds for most of my career, and have for a few years used Vim with Calva, but after getting fed up with the friction of keyboard shortcuts, I have tried to stick to only Paredit while working with Clojure, since structural editing is important for lisps. Are the gains perhaps mostly in the structural editing, not in the structural navigation? How do you guys navigate?

p-himik10:07:37

What's the friction you're talking about between Vim and Paredit? I've been using them both (or rather, IdeaVim instead of Vim) for years just fine. I use Vim where it makes more sense and Paredit in other places. Like in the case above, I'd just use jjjj (`4j` if I'm feeling like typing in numbers). The Vim's Easymotion extension takes things further where I can go to "that character x over there half a screen away" in 4-5 key strokes.

2
didibus10:07:57

I don't exclusively use paredit to move around. But, I've got up/down/left/right set to Ctrl+up, Ctrl+down, Ctrl+left, Ctrl+right. So it's not really 9 commands, I hold Ctrl and than it's 9 movement . In the above case, I would just move the cursor down four lines normally. Or in Emacs, you've got avy jump too where you type the letter to jump too. Like jump to "b and it goes there directly.

joakimen10:07:29

@U2FRKM4TW I'd probably get there much faster in vim as well; I have just heard so much about structural editing and navigation after I started meddling with elisp and clojure, but if this is the extent of structural navigation (I.e. you need to combine structural navigation with "dumb" character-based navigation to be effective) then the premise is a bit flawed, in my opinion. It's still good and precise, but I guess I will have to pick Vim-binds back up again. As for the friction, the vim integration in vscode hijacks ctrl-w in a way I haven't been able to disable, and comes with a boatload of shortcuts that activate on certain editor events, making customizing it to let the Calva-keybinds just work a hot mess @U0K064KQV given that you're both experienced clojurists and both combine character-based navigation with structural navigation, I'll settle on that for now. Speaking of emacs, do you have any recommendation on a starter point for a clojure environment? I have used Doom the most, but abandoned it after the upgrade-process broke on me the nth time.

p-himik10:07:46

No tool is perfect, you have to pick what best suits your needs. I would also suggest looking at the NeoVim extension for VSCode - no clue about Ctrl+W but IME it was easier to configure and use overall.

didibus11:07:24

Structural editing is really useful on top of normal movement. But I've never tried to exclusively navigate around using it. I don't use vim bindings, because I dislike modes. But also most vim navigation is line based, which isn't great for Lisp, and where structural navigation is what you'll want instead. For example, going up a line isn't how you get to insert a statement between two existing one. In a line based lang it would work, but instead in Lisp you need to go up a form and then between them.

didibus11:07:01

I use spacemacs personally, in holy mode (non vim). And have some additional custom shortcuts of my own. But the downside of Emacs is very much that it often breaks, and you need to fix things yourself, etc. The upside is, you can relatively easily learn to debug and fix things yourself.

joakimen11:07:05

@U2FRKM4TW I tried the neovim plugin again a few weeks ago, and it had some other issues, but I will give it some fiddling, gotta make this setup work now, hm. @U0K064KQV I briefly tried Spacemacs before Doom, but it seemed even more complicated (and thus harder to debug) than Doom to me, but will give it another shot at some point. > For example, going up a line isn't how you get to insert a statement between two existing one. In a line based lang it would work, but instead in Lisp you need to go up a form and then between them. This, and the tendency to just "chop text" in vim normal-mode, completely bypassing the paredit rules that uphold the structural integrity, is what kind of led me down the Paredit route. I'll try to find some middleway, thanks for the good feedback!

practicalli-johnny12:07:38

In my view structural editing is to manage changes to the code without breaking the structure of the code. Changes include creating, transposing and deleting expressions Navigation features are secondary to me as there are so many other ways to navigate within Emacs + Evil or Neovim. In the original post example, i'd probably use search, or jump if there were multiple "bar" in the current file. Before I was comfortable with multi-modal editing I used Smartparens and Spacemacs lisp state for structural editing. I added evil-cleverparens to ensure evil commands in normal mode did not break the structure. As confidence and experience grew with multi-modal editing I found some of the simpler structural editing can be done using common multi-modal editing key bindings. I mostly navigate with vim-style normal key bindings like % to jump to a matching paren Some other examples at https://practical.li/spacemacs/spacemacs-basics/vim-style/vim-tips-for-developers/

practicalli-johnny12:07:26

I have been using parinfer recently with Neovim and find it a simpler approach especially with multi-modal editing, assuming you trust parinfer to do the right thing and understand the basic operation

daveliepmann12:07:01

I don't use Paredit for moves like that. Nothing wrong with moving up/down/left/right manually, or just as likely ace-jump-mode. My emacs config is http://github.com/jackrusher/dotemacs (with the supplied user-specific config)

bzmariano13:07:57

I'm also using Parinfer with Neovim, and it's pretty good. No need for extra commands.

gnl14:07:24

I'm not sure about other editors/environments, but in Cursive you can have parinfer and paredit on at the same time (and of course IdeaVIM), so you can use paredit commands or just dd-delete whole lines and let parinfer take care of the parens, depending on what feels more efficient or intuitive in each case.

gnl14:07:34

...and I do a lot of short-distance jumping to whatever I'm looking at through forward and backward search (mapped to Space+j/k) in VIM.

gnl15:07:08

Or if you want to go extra crazy with this, you can buy a Tobii eye tracker and get the https://talonvoice.com/ software to make your cursor jump to whatever you're looking at (I actually bought one a while back, but haven't managed to get around to setting it up yet, plus I'm not sure how well it works on a larger screen).

p-himik15:07:40

At that point, buying pedals should also be under consideration. :)

👆 2
😁 4
oyakushev16:07:34

Could just move the mouse cursor slightly and click, bam, there:)

oyakushev16:07:28

What I also use very often to quickly jump out of a deep form is beginning-of-defun/`end-of-defun` which are bound to C-M-a/`C-M-e` in my setup (not sure if default or custom)

gnl16:07:22

> move the mouse cursor Oh no, he said the dreaded m-word.

😱 8
Mario G00:07:02

> Are the gains perhaps mostly in the structural editing, not in the structural navigation? Short answer: yes I think so. Or at least this is how I found a balance after various experiments. My Emacs/Evil setup is hybrid (meaning: a mess 😉) but in general: • jump around/navigation mostly vim-ish, some plain Emacs, almost nothing Lisp specific (I use Cleverparens&Paredit); • structural edit, hell yes Cleverparens&Paredit 🙂 but also room for Vim&Emacs tricks. But this is me, to each their own discovery of their own favourite setup. Happy experimentation 🙂

Sam Ritchie01:07:36

In emacs I would type C-s bar to get to bar, then C-b to jump back a word

Sam Ritchie01:07:25

And then of course structural editing takes over when I need it, as folks here are saying

Joshua Suskalo17:07:47

Personally I use evil and so I'd just /"bar<RET>

Joshua Suskalo17:07:04

You could also use avy-goto-char-2 and type "b and it'd jump right there

Joshua Suskalo17:07:16

avy is a very good package for jumping around windows and buffers to things you can see

Joshua Suskalo17:07:25

If you did avy-goto-char then " it'd highlight every double quote on your screen and mark them with a letter you can use to jump to the particular one. So in this case it'd be c unless you had more double quotes that sort earlier among your windows

didibus21:07:52

Is there a way with partition-by to know the order it'll partition in?

didibus21:07:23

I'm partitioning by a boolean, and want true to always be the first element, and false the second.

p-himik21:07:47

Sounds like you need group-by then. partition-by preserves the original order.

didibus21:07:38

Ya, already switched to group-by, just wasn't sure if it could also be done with partition

didibus21:07:18

thanks

👍 2
Bob B21:07:41

you could alternatively drop-while false - that's going to drop elements from the seq, but presumably that's what you want if you want the first partition to always be true

jjttjj22:07:14

depends on the details of what you're doing, but this might be useful https://www.juxt.pro/blog/new-medley-partition-fns/