Fork me on GitHub
#beginners
<
2020-07-05
>
nick10:07:56

https://youtu.be/75U3W8Y2zzw?t=270 I was recently watching @seancorfield 's talk named "Real World Clojure Doing Boring Stuff With An Exciting Language Sean Corfield" and the part where he talks about how they do deployment just blew my mind(in a good way). The quote starts at ~ 4:30: > We had to go down :gen-class for a couple of things. > We have tried where possible to do everything through clojure.lang.RT. > So we essentially load up all of the namespaces we're going to expose into our code dynamically. > And just let Clojure get on with it. That has the benefit that we can reload the application without the downtime and it just picks up any new Clojure code and recompiles it. So we can do deployments very easily Sounds like a really neat way of doing zero-downtime deployments(if I got it right). I couldn't find much info on clojure.lang.RT or any examples of how people do it. Any hints to get me unstuck are greatly appreciated.

noisesmith13:07:17

an example of usage here https://github.com/noisesmith/clj-jsvc-adapter/blob/master/src/java/org/noisesmith/Cljsvc.java - there's not too much to get, you use require to make a namespace load, and var to get a function or value out of that namespace

nick13:07:22

thanks I'll check it out. Although I'm not sure how it is related to the question. I don't need to call Clojure from other JVM languages. It is all Clojure

noisesmith13:07:30

one approach I guess would be to use var to pull in clojure.core/load-file which explicitly pulls in new code from that file

πŸ’‘ 3
noisesmith13:07:01

oh, I don't know why you'd need clojure.lang.RT then either, just use load-file

noisesmith13:07:58

RT isn't the public API

πŸ†— 3
nick13:07:27

so it's like, update your source code to a new git tag/branch, connect to existing repl process. Build a list of all files/namespaces and clojure.core/load-file them all? It is oversimplified, just trying to understand the workflow

noisesmith13:07:16

if it's an entire source tree, already on your running classpath, just call (require 'some.ns :reload-all) on the top-level ns

πŸ™ 3
nick13:07:42

Thanks! I'll give it a try

noisesmith13:07:22

I don't know how one would handle deps with a system like this though - Sean hangs out here a lot so he probably has more insight

seancorfield18:07:01

@U0113AVHL2W That's an old talk, based on using an old version of Clojure. We switched to the newer clojure.java.api once it appeared and nowadays we use (the undocumented) serialized-require from 1.10 so require operations are thread-safe.

πŸ™ 3
seancorfield18:07:23

It's also worth noting that we're loading Clojure into a ColdFusion (CFML) app -- which is why we're using this API in the first place.

πŸ‘ 3
seancorfield18:07:43

(and we started doing this back in 2011 -- and we still have a couple of small CFML apps in production that have not yet been rewritten in Clojure so we're still doing this a bit: everything else got rewritten over that time)

nick18:07:25

Got it. Thank you so much for the explanation

futurile11:07:03

I've been trying to use expectations/clojure-test``. Am I understanding it correctly, and expectations can only test for equality. Say I want to test if something is >= 10:

(defexpect digit-test 
  (expect >= 5 (+ 1 9)))
This doesn't work. I can do it in clojure.test with:
(deftest addition-test
  (is (>= 4 (+ 1 9))))
I know I can fall back to using deftest - just checking I've understood correctly that Expectations only handles testing equality.

jsn12:07:59

Judging by the docs, (expect #(>= % 4) (+ 1 9)) should work

jsn12:07:12

or (expect true (>= (+ 1 9) 5))

futurile15:07:48

@UTQEPUEH4 ah yes, 'expect true' definitely works - thank-you.

Nathan Fritch17:07:28

I have a data structure that looks like {:last_insert_id() 1} . I've tried ((symbol ":last_insert_id()) my-structure) and (get-in my-structure [(symbol ":last_insert_id()")]) . But I'm getting nil for both of those. How can I extract this value?

noisesmith18:07:34

if you are using get-in anyway, you could skip whatever step turns last_insert_id() into a keyword and just use strings

Nathan Fritch17:07:28

Oops. I figured it out. I was using (symbol) instead of (keyword)

stopa20:07:14

hey team, i have a v annoying bug with mailgun, where messages they send seems to be an incorrectly formatted string. "<html><head></head><body><div dir=\"ltr\">&#273;&#159;&#143;&#147; this is an example response!</div><br></body></html>" &#273;&#159;&#143;&#147;`` is supposed to be an emoji. If ya'll have some insights, on if there's a way I can fix this on my end, would love thoughts! Initially thought if I can guess w/e encoding this is, I can fix it -- but had no luck so far β€’ i.e: (slurp (.getBytes x "utf-8") :encoding "iso-8859-1")

Lennart Buit20:07:26

That ampersant indicates that the thing following is a so called html entity. For example, a < can obviously not be part of the content of a HTML document (its used for tags, after all), so there is a special syntax, < , indicating a less-than sign as part of the content.

Lennart Buit20:07:11

That said, the sequence you are posting doesn’t make much sense: https://mothereff.in/html-entities#%C4%91%C5%B8%C2%8F%E2%80%9C

stopa20:07:43

I see, thanks for the context. I guess their parser may be bungling something up. It is supposed to be :table_tennis_paddle_and_ball:

Lennart Buit20:07:46

Yeah, so that same website is saying that that could be escaped as &#x1F3D3;

stopa21:07:51

Wait, that does seem to be the emoji I want. Mind explaining to me how the website is saying that? (If there's some fn I can run to escape it into the html, that would be great!)

Mikael Andersson21:07:21

;;:table_tennis_paddle_and_ball: ;;table tennis bat and ball ;;Unicode: U+1F3D3, UTF-8: F0 9F 8F 93 (String. (byte-array [240 159 143 147]))

Mikael Andersson21:07:22

For some reason the first entity in your sample is 273 (hex 111) and not 240 (F0) as the table tennis bat should have. I'm mystified to this part of it. What otherwise seems to have happened is that an UTF-8 byte stream has been treated as (some) 8-bit character set (say iso-latin-1) when rendered to html causing the incorrect entity encoding, as the encoding for a html page without any charset specification is today considered to be utf-8, &#x1F3D3; is afiact the correct entity representation. The given 273 159 143 147 the entity encoding for the separate bytes in the utf-8 entity, as the small code snippet I posted shows.

πŸ‘ 3
stopa23:07:16

Wow, thanks for the deep answer team!

stopa23:07:52

Indeed this off-by-one error looks v wacky. Okay, for now will stick with a hack -- maybe mailgun will get back with something too

Walt Stoneburner21:07:35

I've recently noticed that I'm starting to get less helpful error messages, as they don't tell me the Clojure file where the error occurred -- rather it's in some deeply tested temp directory. Any idea why it would do this? (Did I bork myself by adding (:gen-class) somehow?) e.g., Syntax error compiling at (/private/var/folders/rk/0qy1s9bj16n265fq48k0_z3h0000gn/T/form-init4186707578230173306.clj:1:125).

Walt Stoneburner21:07:54

I'm pretty sure I remember seeing it call out specific .clj files a few days/hours ago. (IntelliJ 2020.1, Cursive 1.9.2-2020.1)

Mikael Andersson21:07:56

Do you get the same if you re-evaluate the entire file (load file in repl menu) or runs sync files in repl before evaluating the expression?

Walt Stoneburner22:07:29

It is happening when I go from a stopped REPL and completely restart the REPL. Happens even after a lein clean.

noisesmith22:07:06

the initial load doesn't use your real file, I typically solve this by not using the init-ns feature, and doing a normal require in the repl

noisesmith22:07:39

after the initial load, it's not lein loading the files, it's clojure, so you get the correct and helpful behavior

Walt Stoneburner22:07:45

So, putting the 'beginner' in the #beginners channel... In IntelliJ, I click on the Run REPL button. And from that clean start, I get a syntax error, but then immediately a Process finished with exit code 1, No nREPL ack received -- and the REPL isn't running. Additionally, I'm not using init-ns. I'm just trying to get the code to compile first and then start the REPL. (you may be thinking I'm further along than I am)

noisesmith22:07:59

there's no such thing as compiling before running the REPL, clojure's repl is the compiler

noisesmith22:07:38

there is an Nrepl server (separate from the default repl) and a compile (which is optional, used in deployment, puts byte-code on disk)

πŸ’‘ 3
noisesmith22:07:53

but for normal interaction the repl is the compiler

noisesmith22:07:01

the thing that's more reliable for iterated development is to turn off the project.clj :init-ns config - it wouldn't be trying to load any of your code if that wasn't set

noisesmith22:07:55

though if the process exits 1 before your repl starts, that could mean an error in compiling your project.clj itself

Walt Stoneburner22:07:00

The light bulb is slowly coming on -- what's the difference between the REPL and nREPL server? ...checking the project.cli. (fyi, that's still a little bit of black magic DSL I haven't fully groked yet)

noisesmith22:07:04

do you mind sharing the file?

noisesmith22:07:28

to see the docs for the project config you can run lein help sample

noisesmith22:07:42

it's a data literal that tells lein how to resolve your deps, and how to run your code

Walt Stoneburner22:07:56

Actually, I think you've just nailed it on several fronts. Making a note about the lein sample. I appear to have told you wrong, as I though the (init-ns) was done from the REPL or in my code. Turns out, found this in the project.clj file.

:repl-options {:init-ns myproject.core}
I suspect I copied that from some helpful website. What exactly is that doing? (I now feel I should remove it.)

noisesmith22:07:44

it's a convenience that loads your ns on startup

noisesmith22:07:59

I don't like it because it points to a weird tmp file instead of your actual source of your problem

Walt Stoneburner22:07:14

Ah, so I'm sitting in "user" by default, and this puts me in my application's name space....

noisesmith22:07:18

and puts your repl in a bad state (or could even crash it), rather than letting you debug and move forward

Walt Stoneburner22:07:40

Just comment that out?

noisesmith22:07:47

right - that config asks that your namespace be loaded up, and for your repl to switch there before you get your prompt

Walt Stoneburner22:07:11

Let me reintroduce the syntax error and see if I can trigger it.

Walt Stoneburner22:07:44

(and import the changes <facepalm/>)

Walt Stoneburner22:07:33

IT WORKED! Thank you so much!!!!!!!!!!

noisesmith22:07:58

btw import exists, and is only for java classes, for clojure code you want require

noisesmith22:07:27

specifically in interactive dev you would use (require 'my.ns :reload) though there's probably a plugin in your IDE that does that behind the scenes for you

Mikael Andersson22:07:18

I knew I'd seen the behaviour somewhere but didn't ever connect it with init-ns, good to know. In cursive I create a REPL command (Add New REPL Command) that syncs/loads namespaces which I bind to a convenient key. They can be set as project specific and run arbitrary repl commands.

βœ”οΈ 3
Walt Stoneburner22:07:26

Actually, I've bumped into both of those issues -- the first was when I tried to get at a real Java class outside the Clojure world.... The second was that I thought I was certainly doing something wrong stopping and restarting the REPL each time I made a minor change.... that require has been so helpful. Thank you again! (I'm trying a slightly larger project to force me into real-world problems and give me a little more practice at the language and the idioms.)

noisesmith22:07:57

very cool - you'll see a pattern in my help that I try to steer people away from the things their tooling does and toward what clojure itself does - that just means I can help more people as I don't know all the tools

noisesmith22:07:14

but if your tool works, cheers

Walt Stoneburner22:07:20

Brilliant. That truly makes a lot of sense. I have this same concern with the other languages I'm more familiar with, in that it allows folks to code with an apparent competency higher than what they really have. And I just experienced it. The tooling was being helpful, but if something goes wrong, there's no way to know where to look if you're unfamiliar with all the inner workings. Thank you for helping to keep me honest.

seancorfield23:07:41

@U051SS2EU I never knew :init-ns had that quirk... one more reason to be glad I stopped using Leiningen years ago, I suppose πŸ™‚

seancorfield23:07:07

I'll have to go dig in Leiningen to understand why tho', unless you can explain?

Walt Stoneburner23:07:15

@seancorfield -- what do you use instead of lein; is there some better tooling I should be aware of, are are you hardcore old-schooling it?

seancorfield23:07:32

I switched from Leiningen to Boot back in 2015 and then to the (new at the time) Clojure CLI / deps.edn in 2018.

seancorfield23:07:02

We have 100,000 lines of Clojure at work that we manage with clojure and deps.edn and a handful of simple tools.

noisesmith04:07:58

I think the big picture explanation is that in order to implement the lein options that require start up code in the target vm, they decided to put all the code in a single external file. init-ns is the most frequently used of those options (and the one that most frequently leads to obfuscated errors, as it's pulling in your own code but obfuscating the source of the error)

johnjelinek23:07:19

hihi all πŸ™‚ What's the equivalent of this in clojure using :gen-class?

public class HandlerCloudFront implements RequestHandler<CloudFrontEvent, String>{

seancorfield23:07:55

Since RequestHandler is an interface, you could just reify it, depending on what you're trying to do.