Fork me on GitHub
#beginners
<
2023-08-11
>
Dallas Surewood13:08:24

When setting up a REPL in production that you can SSH to, is there any reason to use socket REPLs over nREPL?

dpsutton13:08:00

I believe there are good reasons for it. • socket repls don’t “steal your main”. you can continue to use the normal entry point of your uberjar and add some jvm options to get a socket repl up. • you don’t need extra dependencies. Often prod builds won’t have nrepl at all. So you’d need to add it and start it, or your prod builds always have nrepl bundled • nrepl does some subscribing for out which isn’t always cleaned up I don’t think. Can change how your application behaves

seancorfield15:08:07

I agree and we used socket REPLs for a long time in production -- and rlwrap telnet to access them. But that meant we had none of our editor niceties so eventually we decided to add nrepl as a (production) dependency and write a small amount of code that would start an nrepl server at app startup if a JVM property was supplied. The editor complains about the lack of cider support but provides all of the basic eval support you'd expect so we can work from a scratch file in an editor instead of a plain terminal repl. We added portal as a dependency in a couple of production apps too (internal-facing, but they have access to the same DB as customer-facing), so that we can have the full nrepl/portal editor experience with staging and production that we have locally (again, sans cider). Then I automated the whole thing with Joyride for VS Code 🙂 Now I can start my vpn, hit ctrl+alt+b p to start an ssh tunnel and connect to a repl on production and copy the portal port file from production to local, then ctrl+alt+space p to start two portal windows inside VS Code that are powered by production. ctrl+alt+b q would "browse qa" instead of production.

Dallas Surewood15:08:23

Is there a reason you can't use cider? I was just testing with nrepl and seeing if I can get calva to connect to Prod with support, so I can just evaluate stuff in VSCode and have it work on prod

dpsutton15:08:15

it might be CIDER that adds a forking printer on the out that can linger if you don’t shut down correctly.

seancorfield15:08:56

I just don't want all the cider middleware and the "fancy" stuff it does... and, yeah, it can do weird stuff semantically... great for local dev, not so great for a production process.

seancorfield15:08:07

My vscode-calva-setup repo has most of the config/machinery including keymaps, joyride scripts, custom calva repl snippets...

dpsutton15:08:27

i think sometime in the past it used to reset the System out. Seems like it doesn’t do that any more but i have a vague memory that it could change output in some way

dpsutton15:08:39

(joyride to set up ssh tunnels and such is slick)

Dallas Surewood15:08:45

I haven't quite gotten into joyride yet.

Dallas Surewood15:08:59

You use emacs, right Sean? I don't think there's a way to work with nREPL using Calva without cider

seancorfield15:08:00

☝️:skin-tone-2: All the above is VS Code/Calva. I used to use Emacs (on and off for decades).

seancorfield15:08:28

Calva warns that some functionality won't work without CIDER, but all basic eval stuff works fine.

seancorfield15:08:48

See https://github.com/seancorfield/vscode-calva-setup (and maybe some stuff from https://github.com/seancorfield/dot-clojure -- aliases and my local dev repl start script)

Roeniss Moon14:08:58

Hello, I'm following BraveClojure and having some trouble about basic understanding. Help me please!

((first '((first [+ 0]) 1 2 3)) 4 5 6)  ; it raise `Unhandled java.lang.ClassCastException
` I inferred like this:
(first '((first [+ 0]) 1 2 3)) 
; this actually returns (first [+ 0])
; and (first [+ 0]) will return +
; so, ((first [+ 0]) 4 5 6) will return 15
I don't understand why it raise exception, and the real problem is that I have no idea how to debug this error from repl's stacktrace.

dpsutton14:08:14

in a repl, i put my cursor at the end of the form and evaluated it and i get the error

user=> ((first '((first [+ 0]) 1 2 3)) 4 5 6)
Execution error (ClassCastException) at user/eval197627 (REPL:12).
class clojure.lang.PersistentList cannot be cast to class clojure.lang.IFn (clojure.lang.PersistentList and clojure.lang.IFn are in unnamed module of loader 'app')
this error says I don’t know how to treat a list as a function. So somewhere we’re trying to invoke (some-list args) like ((1 2 3) 4) and i don’t know what that should do, so an error sounds correct

dpsutton14:08:31

evaluating a subpart of it i see

user=> (first '((first [+ 0]) 1 2 3))
(first [+ 0])

dpsutton14:08:04

and there’s your issue. you’ve got a quote

user=> '((first [+ 0]) 1 2 3)
((first [+ 0]) 1 2 3)

Ewa Trzemżalska14:08:10

If you would use vector instead it would work as you expect:

((first [(first [+ 0]) 1 2 3]) 4 5 6)

👍 2
Roeniss Moon14:08:12

Thank you dpsutton and Ewa. First, the quote was intentional. I wonder why this is not evaluated natually. Then from the Ewa's hint, I figured out that there is eval function, which is what I needed in this expression. I think I went one step forward with your help! 😄 Have a nice day.

🙌 1
Ewa Trzemżalska14:08:53

Could you paste here your final code?

Roeniss Moon15:08:12

sure!

((eval (first '((first [+ 0]) 1 2 3))) 4 5 6) ; return 15
I guess this is not optimal for this case, but it seems enough for now 😅

👍 1
kennytilton15:08:46

quote exists to block "natural evaluation", but then we (@U05MHC4E6UC) hit a need for evaluation via eval , so there may be a deeper misunderstanding afoot. Where exactly are you in Brave Clojure?

1
Roeniss Moon15:08:47

Is it so? I was on https://www.braveclojure.com/do-things/#Calling_Functions in Chapter 3:

((first [+ 0]) 1 2 3)
; => 6
In the previous part, I learned some data structures including list. So with those combination, I made a hypothesis: • I can make a list with a single quote. • list is not evaluated. Instead, it is treated as a value. e.g., I can't write (1 2 3), but I can write '(1 2 3) • if I write '((first [+ 0]) 1 2 3) instead of ((first [+ 0]) 1 2 3), then (first [+ 0]) would be just a first value of four-sized list ◦ wait, it seems suspicious....(first '((first [+ 0]) 1 2 3)) returns (first [+ 0]) and I thought this is still value. uh... isn't it? • then I guess the eval evaluate the value as a expression (not sure it is called like this) • So I tested (eval (first '((first [+ 0]) 1 2 3))). it shows #function[clojure.core/+] which seems the same as I expeted. • The rest is nothing special. ((eval (first '((first [+ 0]) 1 2 3))) 4 5 6) would be (+ 4 5 6) and it worked like that. While writing this, I found some missing link in my guess: I unconsciously '((first [+ 0]) 1 2 3) and '('(first [+ 0]) 1 2 3) will be the same.
user> (do (println (first '('(first [+ 0]) 1 2 3))) (println (first '((first [+ 0]) 1 2 3))))
; => (quote (first [+ 0]))
; => (first [+ 0])
Ok it's totally different. But really confusing...

valerauko17:08:38

I dislike using quote for "just" writing a list. If i need a list I'll write (list ,,,)

🆗 1
kennytilton17:08:32

First of all. congrats on your exploring and confident nature! Second of all, Beware the REPL!

(first '((first [+ 0]) 1 2 3))
=> (first [+ 0])
;; yay! we extracted the function!
;; Maybe. The REPL can mislead. Better check...

(fn? (first '((first [+ 0]) 1 2 3)))
=> false
;; Yikes. What is it then?

(type (first '((first [+ 0]) 1 2 3)))
=> clojure.lang.PersistentList
;; What list? If we ask the first element we will just get confused. Try the second:

(second (first '((first [+ 0]) 1 2 3)))
=> [+ 0]
;; Ugh. It /is/ a list. Well, at least we got the vector of the function + and 0! Hmmm...

(type (second (first '((first [+ 0]) 1 2 3))))
=> clojure.lang.PersistentVector
;; Booyow! This is starting to make sense. And it holds a function and a long, right?

(type (first (second (first '((first [+ 0]) 1 2 3)))))
=> clojure.lang.Symbol
;; Hunh? I need a sanity check...

(type (first [+ 0]))
=> clojure.core$_PLUS_
;; Hmmmm. One more sanity check...

(type (first '[+ 0]))
=> clojure.lang.Symbol
;; Consistency! And so quote returns nothing but symbols and lists of symbols!!!! Right?

(type (second '[+ 0]))
=> java.lang.Long
;; *sob*
Hth!

kennytilton18:08:17

Bold explorers die young. 🙀 Your brave exploration plunged you into John McCarthy's profound insight, the grand unification of code and data, aka homoiconicity. quote makes lexical code into data. eval brings it back. You could not explore extracting the function part of ((first [+ 0]) 1 2 3)) 4 5 6) without blocking the evaluation by quoting, so you quoted and were doomed. The good news? You have now qualified for the chapter on Macros, where we must juggle deftly code as data to produce code. Courage.

🙌 2
Roeniss Moon04:08:46

Thank you for such a huge explanation kennytilton! To be honest, I dind't understand all of your saying but it is clearer to certain extent than before, which makes me feel better. I hope you have smooth weekend. 👍

🌴 2