Fork me on GitHub
#clojure
<
2017-09-19
>
deg10:09:55

(This is likely a cljs, cljsbuild or version clash problem, but it was triggered by moving to clojure 1.9.0-beta1, so posting here)... Moving https://github.com/deg/trilystro to the new Clojure beta caused cljsbuild to fail with:

Sep 19, 2017 1:46:59 PM com.google.javascript.jscomp.LoggerErrorManager println
SEVERE: /home/deg/Documents/git/projects/trilystro/target/cljsbuild-compiler-1/cljs/core.js:3579: ERROR - Parse error. primary expression expected
case ##Inf:
      ^
This is in the function cljs.core/hash

sfalcon12:09:56

hi guys, what’s your preferred dsl/library for interacting with SQL?

Alex Miller (Clojure team)12:09:25

@deg This is a known thing re cljs - might ask over in #clojurescript or #cljs-dev on status

deg12:09:58

Thx, will do.

deg13:09:51

@alexmiller - Yup, @mfikes had the answer on #cljs-dev. But, looks like this will bite all ClojureScript projects that try the beta, until ClojureScript is updated to match. Can a warning be added to the beta release notes, meanwhile?

Garrett Hopper15:09:03

@sfalcon I personally prefer using Yesql over a Clojure DSL for SQL. It allows you to write everything in actual sql files and gain all SQL tooling from that.

sfalcon15:09:40

will have a look thank you

Garrett Hopper15:09:30

I was just looking into that the other day @bostonaholic. What's the reloading situation like?

danm15:09:51

HoneySQL gave us the advantage of being able to programmatically build up SQL queries. Like have a base one and then add a join onto that and a different where clause to filter and such. Not sure how you'd do that with YesQL

Garrett Hopper15:09:39

Any opinions on tools.namespace reloading (possibly reloaded.repl) vs plain repl reevaluation? I'm using reloaded.repl at the moment, but I'm starting to feel as if going back to plain repl evaluation might be simpler. Though I'm not sure if changed to my ring handler would be picked up without restarting the server.

sfalcon15:09:56

interesting, so the trend is to use libraries that use SQL as data structures as opposed to what Korma (for example) does

sfalcon15:09:42

I used Korma before but something about the DSL feels off to me, will have a look at those libraries as well thank you

aisamu15:09:54

@sfalcon @michaeldrogalis elaborates a bit more on this Korma vs HoneySQL (data-centric vs otherwise) on the (very nice) Clojure Remote keynote: https://youtu.be/kP8wImz-x4w?t=1409. Thanks for that, mr. Drogalis!

sophiago17:09:34

I'm quite confused about this example...I have one function that appears to return a long, yet when I call inc (or any other numerical function) on it the compiler tells me it's an Object: https://gist.github.com/Sophia-Gold/6e0abe5080b4db078220031ff3c95d6d What am I missing here?

Alex Miller (Clojure team)17:09:20

by default the params and return vals of all functions are of type Object

Alex Miller (Clojure team)17:09:36

you’ll need to type hint the return to get something different

Alex Miller (Clojure team)17:09:44

(defn foo ^long [String string] ...)

sophiago17:09:14

Ah, that's a simple answer. Thanks, Alex.

Alex Miller (Clojure team)17:09:54

the inner loop of foo will used boxed longs as written now, but that could be rewritten with loop/recur to use primitive longs

bronsa17:09:55

type will also always box the object so it will never return a primitive type

sophiago17:09:05

Oh, interesting. I always default to thinking reduce would be more performant through using the Iterator interface.

sophiago17:09:14

@bronsa, that's not true

bronsa17:09:31

i'm talking about Long vs long

sophiago17:09:04

You can see in this example that it returns a Java long. What am I misunderstanding?

bronsa17:09:35

primitive means a primitive long type, java.lang.Long is a boxed object, that's the distinction i was making

sophiago17:09:58

I think you mean Clojure long vs. Java long...

dpsutton17:09:27

;; => (type (foo "baz"))
;; => java.lang.Long

dpsutton17:09:34

notice the capital L in your output

sophiago17:09:55

The capital L refers to a boxed long?

bronsa17:09:57

there's no such thing as a clojure long

sophiago17:09:08

@bronsa sorry, you're right about that. I for some reason thought there was clojure.lang.long

bronsa17:09:31

the jvm has a primitive long type (which is not an object type) and a boxed class java.lang.Long, type will never return long even if a function returns a primitive long, because type only works on objects and thus will cause a primitive value to be boxed

sophiago17:09:14

Gotcha. And I assume class and instance? are the same?

didibus17:09:24

If type can't give you the correct type of primitives, is there anything that can?

sophiago18:09:02

Well, :warn-on-boxed will.

bronsa18:09:09

it would have to be a macro that interopped with the compiler, or a compiler feature

sophiago18:09:40

That's a compiler feature, though.

bronsa18:09:54

no function can because it would have to accept both objects and primitive values, and the clojure compiler would cause values to be either boxed or unboxed as it deems appropriate, so not retaining the real type

bronsa18:09:10

@sophiago yeah, but it doesn't do what @didibus was asking

didibus18:09:18

A little annoying when you want to validate you're not using boxing

bronsa18:09:32

if that's all that you care about than as @sophiago said, warn-on-boxed/`warn-on-reflection`

noisesmith18:09:33

@sophiago what are you calling a compiler feature? I get it now, never mind

sophiago18:09:40

@didibus (set! *unchecked-math* :warn-on-boxed)

didibus18:09:54

Ya, I guess.

didibus18:09:42

And whats with this:

(defn ^long foo [x] (int x))
(type (foo 12))
=> java.lang.Integer
?

bronsa18:09:57

that's not ac correct type hint

bronsa18:09:03

move it from foo to the argvec

bronsa18:09:14

(defn foo ^long [..

didibus18:09:01

Damn, all this time, using the wrong syntax lol

bronsa18:09:12

it's only wrong for primitive type hints

bronsa18:09:30

but I suggest using it even for non primitive type hints

didibus18:09:02

Primitive type hints have a different syntax?

bronsa18:09:18

both primitive and non primitive can work in either place

bronsa18:09:46

but because the metadata on var names will get evaluated, you'd have to (defn ^{:tag 'long} foo .. (i.e. explicitly quoting it)

bronsa18:09:18

otherwise you're tagging foo with the value of the long function rather than with the long symbol

Alex Miller (Clojure team)18:09:22

var meta is evaluated. Function type hints are not.

bronsa18:09:47

moreover, the compiler will not emit primitive versions of the function if you tag the var meta rather than the argvec

Alex Miller (Clojure team)18:09:24

I think the takeaway is that you should nearly always type hint the function return, not the var

bronsa18:09:30

@alexmiller sorry i''m not understanding, are you saying what I've said is wrong? because it isn't :)

bronsa18:09:39

s/nearly// IMO

Alex Miller (Clojure team)18:09:40

no, I’m trying to agree with you :)

bronsa18:09:57

misunderstood what you meant by "function type hints" then

didibus18:09:11

Hum, so would the resulting cast be around the var access instead of the function return?

Alex Miller (Clojure team)18:09:12

I meant “the type hints interpreted by defn”

bronsa18:09:17

yep gotcha

didibus18:09:28

Actually, I'm definitly a bit confused about type hints. I've noticed somtimes you can type hint wrongly, and all works, no warnings or errors, sometimes you get runtime type errors.

bronsa18:09:30

what happens in (defn ^{:tag 'long} foo ..) case is that foo returns an Object and at callsite it might get unboxed as a long, if the compiler deems it useful/necessary

bronsa18:09:55

@didibus yeah, this (afaict) is because of clojure's GIGO attitude. I don't particularly like it in this case (I think we could safely warn at compiler time) but it's gotten a bit better than it used to be

Alex Miller (Clojure team)18:09:36

it’s not GIGO, it’s because it’s a hint, and the hint might be wrong

Alex Miller (Clojure team)18:09:58

you might not know it’s wrong till runtime

bronsa18:09:02

I'd consider it GIGO when the type hint can't ever be right, not to warn

didibus18:09:09

So does a wrong hint detected by the compiler reverts it back to using reflection?

Alex Miller (Clojure team)18:09:10

so you can fall back to Object

bronsa18:09:21

well, if something is definitely not a type/class at compile time then it's not a type hint

bronsa18:09:42

I can't see how the compiler accepting a funcntion as at type hint w/o warning/erroring out is not GIGO

Alex Miller (Clojure team)18:09:56

I’m in full support of that being an error

bronsa18:09:26

patch incoming then :)

bronsa18:09:11

(gah, I can't write function anymore apparently, fifth time I write funcntion)

didibus18:09:17

(defn foo ^String [x] (int x))
This still works, no warning or error.

bronsa18:09:39

that is not the case I'm arguing should warn

bronsa18:09:47

that is a validly formed type hint, it's just a nonsensical one

bronsa18:09:06

what if int got redefed to return a string

bronsa18:09:20

then that hint would be correct at some point

Alex Miller (Clojure team)18:09:20

it’s the (defn ^long foo ...) one

noisesmith18:09:30

it’s more like (defn foo ^clojure.string/join [x] (int x)) - the hint is a function not a type

didibus18:09:59

Right, well ya, that I agree should even more so be warned or even error

didibus18:09:02

But, about the other one, I understand maybe it compiling, but why does it work once its compiled to jvm byte code?

bronsa18:09:27

depends on your definition of working!

bronsa18:09:36

once you start doing interop, the compiler will assume that type hint to be correct

bronsa18:09:45

and will dispatch to String methods

bronsa18:09:56

if it can't match one, it will use reflection

Alex Miller (Clojure team)18:09:01

@bronsa patch welcome for https://dev.clojure.org/jira/browse/CLJ-1674 or whatever other jira is out there for this problem

bronsa18:09:09

but if, say, Integer and String had 2 methods with identical signatures, than that would fail at runtime

didibus18:09:24

I mean, in java you can't type the return to string and have it return int. So does clojure ignore the hint amd still returns Object?

bronsa18:09:53

type hints are not static types

bronsa18:09:18

the compiler enforces type hints only if/when it needs to

bronsa18:09:37

and for function return types, it only enforces them when that value is used in an interop form

bronsa18:09:04

e.g. (.foo (bar)) will enforce the type hint on the return type of bar (at callsite of foo!) but (identity (bar)) won't

bronsa18:09:07

because it would be useless

bronsa18:09:27

type hints are only to prevent reflection (and in some cases, boxing), they're not to enforce static types

bronsa18:09:49

and are not guaranteed to cast/box/unbox

didibus18:09:13

I see, okay, that explains I think the inconsistency of wrongly typed type hints I've seen. It'll fail at runtime with java interop.

bronsa18:09:17

an example of this behaviour is

user=> (deftype Foo [a])
user.Foo
user=> (deftype Bar [a])
user.Bar
user=> (defn ^Foo x [] (Bar. 1))
#'user/x
user=> (x)
#object[user.Bar 0x4983159f "[email protected]"]
user=> (.a (x))
ClassCastException user.Bar cannot be cast to user.Foo  user/eval158 (NO_SOURCE_FILE:8)

bronsa18:09:59

the incorrect type hint doesn't cause (x) to fail, it causes host interop on (x) to fail

didibus18:09:17

Whats the idea of not enforcing the hint to always box/unbox or cast ? I don't see a negative in enforcing, except you'd know quicker that you hinted it wrong

bronsa18:09:24

well, the short answer is: type hints are not type assertions, the long(er) answer is: the compiler might now better or enforcing could affect redefinition

bronsa18:09:02

and if you want enforcing I guess spec is better suited at that

didibus18:09:30

(defn foo ^String [x] (int x))
(type (foo 12))
Id expect this to be equal to
Object foo(Object x) { return (String)((int) x); }
and fail at the cast at runtime

bronsa18:09:08

no, again, type hints are neither type assertions nor type casts

bronsa18:09:42

they sometimes act like either, but at the discretion of the compiler

bronsa18:09:14

to do what you want, use (cast String (int x))

didibus18:09:16

I'm not talking static checks. But runtime type errors. Ok, ya I understand, so they're unpredictable and mostly for interop and certain unboxing situation.

bronsa18:09:32

as to enforcing at return type vs callsite -- I believe it's a performance compromise of sorts

bronsa18:09:47

to enforce at return type would require emitting checkcasts at every callsite

bronsa18:09:49

vs only when needed

didibus18:09:27

I understand how they work now. Thank you.

didibus18:09:08

I guess I'll restrict their usage to only when the compiler warns for boxing or needs it to dispatch.

bronsa18:09:18

that is when you should be using them, yes

didibus18:09:29

Some people use them as programmer hints, like documentation. But that had mislead me, because sometimes the hint is wrong, yet all works. I see that's a pretty bad use of them now. Spec is the way to go.

sophiago18:09:37

Ha, had to step away for a phone call. Glad to see this discussion continuing 🙂

sophiago18:09:06

I actually had a related question. Is anyone familiar with Zach Tellman's primitive-math library? I'm wondering exactly how it differs from using unchecked-math in clojure.core. I'm not sure how long the latter has been around, but was under the assumption that library was still useful for providing reflection warnings for math operations.

Alex Miller (Clojure team)19:09:08

The warn-on-boxed stuff was kind of done about the same time as primitive-math (independently). The warn-on-boxed stuff will cover you in most situations, but it’s not perfect and won’t catch all cases (particularly in return paths). I have not looked at primitive-math closely enough to know for sure, but it may be useful in some cases that are not well-caught by warn-on-boxed.

Alex Miller (Clojure team)19:09:19

I have nothing against it :) (but there is some overlap)

sophiago19:09:45

That's pretty much exactly what I told someone in a GitHub issue who was trying to tell me they're exactly the same (with more color that reflection could occur in the return paths) so happy to know I was mostly right 🙂

sophiago19:09:55

Oh, I suppose I should also mention that afaik primitive-math is more comprehensive. For example, it includes equality and bitwise operators.

hmaurer19:09:20

@alexmiller Hi! I was just reading you blogpost http://insideclojure.org/2014/12/15/warn-on-boxed/ and tried to use no.disassemble with boot but requiring the dependency doesn’t seem to be enough (some jvm flag is missing I think, looking a the lein plugin)

hmaurer19:09:49

Could you guide me on how to set it up? Or is there another tool I can use for the same purpose?

reborg19:09:32

it requires an instrumenting agent at startup

reborg19:09:19

lein-disassemble is an easy @hmaurer then lein repl then (require '[no.disassemble :refer [disassemble]])

hmaurer19:09:34

@reborg I would like to use it with Boot

hmaurer19:09:18

actually, could this cause an issue with boot’s approach of loading dependencies after the JVM is started? @seancorfield

hmaurer19:09:36

since I would need to pass the agent flag to the JVM that will be running boot

seancorfield19:09:26

You'd specify the Java agent option via BOOT_JVM_OPTIONS. Pass -javaagent:/path/to/the/nodisassemble.jar

seancorfield19:09:28

(you'd need to run Boot at least once without that in order to download the JAR to .m2/repository and then you'd have the (long) path to the JAR)

hmaurer20:09:07

@seancorfield thanks. So something like this? BOOT_JVM_OPTIONS='-javaagent:~/.m2/repository/nodisassemble/nodisassemble/0.1.3/nodisassemble-0.1.3.jar'

sophiago20:09:31

Sorry, yet another boxing question... Are there best practices for converting characters to numbers? Using the primitive-math casting operators throws an error so I have to switch to those from clojure.core. Not sure if this is something to log an issue with over the primitive-math casts or whether it falls under one of those cases where I'm just not being warned about reflection when using the core versions.

seancorfield20:09:50

@hmaurer Example

(! 687)-> BOOT_JVM_OPTIONS="-javaagent:/Users/sean/.m2/repository/nodisassemble/nodisassemble/0.1.3/nodisassemble-0.1.3.jar" boot -d nodisassemble repl
...
Initializing NoDisassemble Transformer
boot.user=> (require 'no.disassemble)
nil
boot.user=> (in-ns 'no.disassemble)
#object[clojure.lang.Namespace 0x5c26a243 "no.disassemble"]
no.disassemble=>  (println (disassemble (fn [])))
//  (version 1.5 : 49.0, super bit)
public final class no.disassemble$eval2043$fn__2044 extends clojure.lang.AFunction {
  
  // Method descriptor #6 ()V
  // Stack: 1, Locals: 1
  public disassemble$eval2043$fn__2044();
...
no.disassemble=> 

seancorfield20:09:10

I think you'll need the full path, not ~

hmaurer20:09:21

@seancorfield yep I just realised that. It works now

hmaurer20:09:44

Unrelated question (well, related to the discussion we had earlier on uberjars): how do you usually deploy boot applications to production?

seancorfield20:09:53

I love Boot for experimenting with new libraries 🙂 No need for a project folder or anything 🙂

hmaurer20:09:59

Yes it’s brilliant!

seancorfield20:09:37

Mostly we build uberjars. @alexmiller's comment about them being "evil" notwithstanding, it's the most convenient way so we only need Java and the JAR file on the target system.

seancorfield20:09:26

(that said, we still also deploy .clj source files since we use them inside other apps that predates our uberjar approach)

hmaurer20:09:41

Could you imagine a jar downloading its dependencies on startup using boot? Or am I misunderstanding something?

didibus17:09:36

Don't do that, even if you could. Try to have reproducible builds, which leaves zero chance of anything having differed. Better to resolve all things beforehand, and then copy those in one go to your hosts. That's the only way to be 100% sure all hosts are perfectly in sync.

seancorfield20:09:47

So we still have boot repl available on production too (and boot whatever-task stuff as well)

seancorfield20:09:26

@hmaurer What do you gain over having the source of the project there and just doing boot run or whatever?

seancorfield20:09:37

What would that JAR be?

hmaurer20:09:37

Nothing it seems

hmaurer20:09:03

I should have asked: would it make sense to use boot run from source in production

seancorfield20:09:20

Define "make sense". There are pros and cons 🙂

hmaurer20:09:30

What would be the cons? 🙂

seancorfield20:09:02

The cons include deploying all your source etc rather than just a single file (which may make your build process more complex); requiring Boot be installed on all your servers; requiring that your servers can access Maven Central and Clojars at all times; ...

seancorfield20:09:26

That last one was enough for us to run our own Archiva instance acting as a caching proxy for Clojars (before Clojars got the nice, new, redundant CDN implementation for the repo) and all of those together with a desire to move processes to Docker pushed us to use uberjars instead.

seancorfield20:09:40

(that said, we haven't actually made the move to Docker yet)

sophiago20:09:38

@alexmiller sorry to ping you, but wrt to my last question do you know whether something like (map (comp long char) "foo") is one of those cases where the compiler doesn't throw an error on boxing? I suspect this may be the case since all the primitive-math version of long does is call unchecked-long from clojure.core, which throws an error where as cc's long does not.

Alex Miller (Clojure team)20:09:45

anything you do with seq functions will only use objects

noisesmith20:09:39

just curious, what does (comp long char) accomplish that long doesn’t?

Alex Miller (Clojure team)20:09:05

I don’t think it will do anything different

sophiago20:09:06

Yeah, I just realized since I used map to convert from a string I should probably mention I get the same erorr with just (primitive-math/long \a) whereas (long \a) doesn't throw a boxing warning.

Alex Miller (Clojure team)20:09:31

the boxed warnings will only happen if you’re calling a numeric operation

Alex Miller (Clojure team)20:09:41

and map is not one of those

sophiago20:09:34

My clarification was meant to say it happens outside of being passed to a higher order function.

sophiago20:09:50

@noisesmith I'm using Zach Tellman's primitive-math library so for that long you do actually need to cast to a char first

noisesmith20:09:08

but it is a char already?

Alex Miller (Clojure team)20:09:44

long is handled in RT, so it’s not caught by the warnings in Numbers

sophiago20:09:45

I assume it has to do, again, with being an argument to map.

sophiago20:09:04

@alexmiller thank you. That's exactly what I needed to know.

Alex Miller (Clojure team)20:09:44

I think you’re going to end up passing a Character object into RT.longCast(Object)

noisesmith20:09:45

so if you just use (map long "abc") with clojure.core/long it just works, but you need (comp long char) instead because of primitive-math?

Alex Miller (Clojure team)20:09:17

none of these options are going to giving you a sequence of primitive values regardless, so I’m not sure it really matters

sophiago20:09:38

Yes, that's what I was trying to determine.

noisesmith20:09:40

yeah- you can’t put a primitive on the stack, so if you are using map it’s boxed, guaranteed

sophiago20:09:16

Sorry, I just provided a bad example to begin with, but got the answer I needed.

Alex Miller (Clojure team)20:09:18

it’s hard to recommend something different, b/c it matters a lot what you’re going to do with the result

noisesmith20:09:22

(I guess you could make an array transducing context and use a map transducer - or would the map transducer still force the boxing?

Alex Miller (Clojure team)20:09:54

(although we’ve talked about primtiive transducing options)

Alex Miller (Clojure team)20:09:07

if your goal is to produce a collection, you could either use a Java long[] or a primitive vector of longs. In general, the latter tends to force boxing on use anyways (through the vector APIs) so you primarily get only memory savings.

sophiago20:09:50

Hmm. Does this "primitive transducing" discussion have any bearing outside of a higher-order context? Before Justin brought that up I assumed I was stuck with boxing for chars, but I'm not sure whether you're suggesting something from the java.lang.Character.

Alex Miller (Clojure team)20:09:15

generally, if I’m trying to super optimize all this stuff, I’d use long-array or maybe int-array here (depends on whether you just care about ascii or full unicode)

Alex Miller (Clojure team)20:09:34

this is a particularly tricky area of Java

sophiago20:09:45

Right. I'm not going so far with optimization here to use unboxed sequences (and have found vector-of to be almost always useless for the reason you mentioned).

noisesmith20:09:03

fascinating

user=> (doto 'primitive-math require in-ns)
primitive-math
primitive-math=> (long \a)

ClassCastException java.lang.Character cannot be cast to java.lang.Number  clojure.lang.RT.uncheckedLongCast (RT.java:1464)
primitive-math=> (long (char \a))

ClassCastException java.lang.Character cannot be cast to java.lang.Number  clojure.lang.RT.uncheckedLongCast (RT.java:1464)
primitive-math=> (map (comp long char) "hello")

ClassCastException java.lang.Character cannot be cast to java.lang.Number  clojure.lang.RT.uncheckedLongCast (RT.java:1464)

noisesmith20:09:26

I’m sure I’m doing something silly here

sophiago20:09:29

Your first two cases were the behavior I was asking about here. The third one for some reason works for me.

sophiago20:09:16

No. Scratch that. You're correct on all counts.

sophiago20:09:54

There is also Long/parseLong, but that only works for numerical strings. Similar to (map #(Character/digit %) "123")

sophiago20:09:58

Err, sort of similar. You get the point.

noisesmith20:09:01

@sophiago it seems like without using clojure.core/long etc. the easiest way to get a number from a char in a string is

=> (Character/codePointAt "foo" 0)
102

noisesmith20:09:25

but that may be doing something wrong with unicode contexts

noisesmith20:09:24

actually

=> (Character/codePointAt "☃" 0)
9731

sophiago20:09:22

This is for hashes so I'm fine with just ascii

sophiago21:09:28

Oh, it doesn't seem to work in higher-order functions, e.g. (map-indexed #(Character/codePointAt %2 %1) "foo") so I'd need to use loop/recur. Plus I'm not even sure it saves me from boxing wrt to the individual elements.

noisesmith21:09:09

the boxing would depend on what you do in your consuming function, and I would use (map #(f (Character/codePointAt s %)) (range (count s))) - if f knows how to use a numeric value unboxed, it would work I bet

noisesmith21:09:42

primitive-math=> (let [s "hello, world!"] (map #(inc ^int (Character/codePointAt s %)) (range (count s))))
(105 102 109 109 112 45 33 120 112 115 109 101 34)

sophiago21:09:22

Oh, duh 😛

sophiago21:09:43

Thanks. I'll compare. I sort of suspect it's not worth it since I'm just dumping them into a sequence, but will test.

sophiago22:09:52

@noisesmith postmortem: worth adding a helper function for, but not significantly faster for any use cases and slower for some. Regardless, thanks for the tip.

seancorfield20:09:03

If you deploy an uberjar, you can still have a REPL in production of course (start it with the Socket REPL Server options) -- or just run the uberjar with no options (we don't AOT, and we set :main to clojure.main so an uberjar can run clojure.main to get a REPL -- or we can use -m my.namespace to run the -main in that namespace).

hmaurer20:09:59

@seancorfield can you start a repl in a running production system if you didn’t start the uberjar with the socket repl server option?

noisesmith20:09:45

there’s a namespace to use to start the socket server by hand, and of course you can use clojure.tools.nrepl.server to make an nrepl server instead

hmaurer20:09:07

@noisesmith can you plug into a running jvm like that? I am not familiar with how those things work

noisesmith20:09:16

The clojure process can call the functions to start a server. If you really need to monkey your way in, a debug connection could be used to access clojure via interop from the java side and make it do so I guess…

seancorfield20:09:52

In the past we've had a way to tell a process to start a REPL (and to stop it again) so we can have a REPL open when we need it.

seancorfield20:09:30

(that has varied depending on the process)

seancorfield20:09:37

Mostly, for debugging, we'd just restart a process with the socket REPL server JVM options if we needed that. Or just start a standalone REPL while ssh'd into the server, for local interactions.

seancorfield20:09:25

(running a REPL server in a live, production process comes with all sorts of caveats about security and risk and so on and so forth... we have -- occasionally -- applied a hot fix via the REPL by sending a new function defn but... "ultimate power" and all that... generally not recommended practice at all 😐 )

dominicm21:09:00

This is the power of a lisp machine. 0 downtime deploys are much easier if you can live reload code this way.

deactivateduser19:09:28

aka "extensive downtime deploys when you mess it up" 😉

seancorfield21:09:22

@U0MDMDYR3 Heh, just send the old defn back into the running image 🙂

norman21:09:40

I have no qualms about leaving a localhost nrepl on production servers. It’s invaluable.

bja21:09:29

@seancorfield we maintain our processes listening on localhost nrepl ports and use ssh to control them. in rare circumstances we apply hotfixes or debugging that are urgent or otherwise problematic. just another component that spins up an nrepl server. There are caveats and we don't do it lightly, but the power has saved my company a lot of money over the last 4 years.

seancorfield21:09:44

@norman @bja Glad to hear of others using a live localhost REPL for this!

noisesmith22:09:00

yeah - it requires discretion, but can lead to sorting out weird production issues faster

norman22:09:36

When we had just a few production nodes, we would hot patch fixes regularly. There’s also nothing like being able to instrument or sample live data. Obviously you need to know your tools well and tread lightly, but there’s nothing like being able to go from bug report to fixed in production in 5 minutes.

hmaurer23:09:48

@norman doesn’t it become a bit of a nightmare if you apply ad-hoc patches on production?

hmaurer23:09:05

like, don’t you lose track of what code is running?

norman23:09:39

After the fire is out we will get packages built and deployed, but that could be a day or two later

norman23:09:07

Now we just have too many production nodes to do this, but our infrastructure for fixes is more sophisticated

norman23:09:52

Today it could take half a day to get a fix committed, built in CI, approved by QA and deployed by ops

norman23:09:54

in the best case

norman23:09:02

I miss the old days 🙂

norman23:09:22

We still use prod nrepl, just not for fixes

norman23:09:05

Hand fixing 3 heads takes a couple minutes. Patching 20 is less fun manual. It all depends on the scale

the2bears23:09:19

@hmaurer if you maintain the git commit as a value in your running code, you can sync your local version. You now have a nice base with which to apply a fix, test it, roll back if process demands, then push the fix through proper channels.

norman23:09:56

Yes, I would always commit changes for reference