Fork me on GitHub
#clojure
<
2022-11-02
>
respatialized00:11:38

This is kind of an open-ended question, but I was wondering about the idea of "functions as immutable values," as defined by their s-expression source and lexical environment. To give an example, functions with identical sources don't compare for equality in the same way as a function bound to a name would be: >

user> (let [f (fn [] nil)] (= f f))
> => true
> user> (= (fn [] nil) (fn [] nil))
> => false
One could imagine the latter working in a similar way to the former, where even if defined in different places the functions are the same value and compare for equality in an expected way. I can see how this idea might be totally impossible to accomplish without breaking the core of the language. So I guess I'm just curious as to why it wouldn't be possible, given the way Clojure is written, to make functions immutable with respect to the source + lexical environment.

hiredman01:11:54

If you look at the languages used in proof assistants that use dependent types they need something like this for type checking

phronmophobic01:11:11

Even if it were possible, it's probable that it would add a lot of bloat in terms of memory and efficiency

hiredman01:11:32

What you are talking about is some kind of value based equality, instead of reference equality

hiredman01:11:48

functions are already immutable

andy.fingerhut01:11:11

We're not talking about deciding whether two functions with different definitions have the same behavior, are we? CS theory tells us that is an undecidable problem, and you are likely aware of this, so I am guessing that is not what you are hoping for here.

1
andy.fingerhut01:11:22

The Clojure compiler doesn't currently save the S-expression used when defining a function in memory anywhere. If it were saved somehow with the function definition, then of course comparing those S-expressions for value-equality like other nested collections could be done.

andy.fingerhut01:11:49

Defining exactly what equality for "lexical environments" means seems like it could get tricky pretty quickly, perhaps, depending on what you wish for there.

andy.fingerhut01:11:12

For example, if direct linking is enabled in the Clojure compiler, then I'm pretty sure that defining a function that mentions another function by name f inside of it, then later redefining f in the REPL, then later defining a different function using the same expression as the first, would leave you with equal S-expressions, but different lexical environments.

👆 1
👀 1
andy.fingerhut01:11:32

However, if you did the same sequence of operations with direct linking disabled in the Clojure compiler, then I think the lexical environments of the two might be equal.

Macroz08:11:29

If you are interested in this topic then Unison language may be interesting to you https://www.unison-lang.org/learn/the-big-idea/

respatialized13:11:20

Unison was the inspiration for this question 😉

👍 1
Macroz08:11:47

I don't believe that would work out for Clojure, that maintains a very strict idea of backwards compatibility (and it's a good idea to do so), but there is nothing to prevent this to become norm sometime in a Lisp far far away.

Jacob Emcken10:11:30

I have a test case that triggers a multimethod. The multimethod is "extended" in the namespace my-app.extended. The specific test requires this "extension" to have been loaded. If I do the following, my test works:

(ns my-app.test
  (:require [clojure.test :refer [deftest is]]
            [my-app.core :as sut]
            [my-app.extended]))

(deftest ...
If I do the following, it doesn't work:
(ns my-app.test
  (:require [clojure.test :refer [deftest is]]
            [my-app.core :as sut]))

(require '(my-app.extended))

(deftest ...
I would like to understand why?

jkxyz10:11:08

Is that the correct way to invoke require? What if you do (require 'my-app.extended)

delaguardo10:11:52

require accepts the same arguments as for ns :require attribute. So it is ok to call it with '(my-app.extended)

delaguardo10:11:16

there are few useful functions to inspect current state of defmulti, which is afaik mutable construction defined as defonce: methods , get-method , remove-method You can check if your multimethod have desired implementation during the test

delaguardo10:11:15

sometimes it is useful to set defmulti var to nil and reload everything once again Normally I put (def multi-name nil) right above defmulti definition to reset its state via evaling whole namespace

Jacob Emcken10:11:13

For unknown reasons my test works when using @UE72GJS7J suggestion 😮 and stops working as soon as I add the parans again I used the parans because that is what the first example on https://clojuredocs.org/clojure.core/require tells me to:

;; Require  and call its file function:

user=> (require '())
user=> ( "filename")
#<File filename>

Jacob Emcken10:11:59

so, I guess there is a difference somewhere 😅 It isn't the first time these multimethods gives me the impression of being a bit fickle @U04V4KLKC thanks for sharing your tricks @UE72GJS7J thanks for the suggestion

jkxyz11:11:49

The example on ClojureDocs is wrong, but it works because is already loaded. The docstring says that libspecs should be vectors or symbols, and lists are used for namespace prefixes. (require '()) doesn't do anything because there are no symbols or vectors after the prefix. But you could require it like (require '(clojure.java io)) where clojure.java is the prefix. For example using clojure.set which is not loaded already by the REPL:

Clojure 1.10.3
user=> clojure.set/rename-keys
Syntax error (ClassNotFoundException) compiling at (REPL:0:0).
clojure.set
user=> (require '(clojure.set))
nil
user=> clojure.set/rename-keys
Syntax error (ClassNotFoundException) compiling at (REPL:0:0).
clojure.set
user=> (require 'clojure.set) 
nil
user=> clojure.set/rename-keys
#object[clojure.set$rename_keys 0x1d035be3 "clojure.set$rename_keys@1d035be3"]

jkxyz11:11:53

I updated the ClojureDocs example

❤️ 3
Jacob Emcken12:11:41

It is getting better everyday... thanks for taking the time to not confuse noobs

❤️ 2
delaguardo12:11:51

also worth to file a question at http://ask.clojure.org. I think it is a bug in require to return normal result when args are badly shaped.

Joshua Suskalo15:11:01

So the parens vs not parens is actually known behavior, and it would work as you want if you used a vector instead of a list.

Joshua Suskalo15:11:02

The reason is that a list in a require like this is considered a prefix list, which is an obscure version of the require form that treats namespaces as hierarchical, which is why they are no longer encouraged to be used, since you are encouraged not to think of namespaces like this.

Joshua Suskalo15:11:49

(require '(clojure.core
           [walk :as walk]
           [zip :as z]))

Ben Lieberman16:11:24

Are there cases where extend-type should be preferred over extend-protocol? I see that the latter expands to calls to the former but I see examples in the docs of extend-type being used directly.

hiredman16:11:18

sometimes you want to extend a few protocols to the same type, sometimes you want to extend the same protocol to a few types

👍 1
Alex Miller (Clojure team)16:11:37

Neither is preferred - which you should use depends on the situations as described by @U0NCTKEV8

👍 1
kwladyka19:11:36

What is your favourite way to run things parallel for IO operations which takes hours? pmap is cool, because is simple and (+ 2 (.. Runtime getRuntime availableProcessors)) but hmm it looks a little odd. I don’t care about what will be returned by functions which I want to run. (Thread. ...) but then there is no control for availableProcessors async is powerful tool, but usually overkill

(pmap (fn [f] (f))
      [#(dc-sync-db/sync-db-spot-trades-from-csv a b)
       #(dc-sync-db/sync-db-spot-candlesticks-1m-from-csv a b)
       ...
       ...
       ...
])
simple ver:
(map (fn [f] (f)) [#(println "I do it 6 hours") #(println "I do it 1 hour")])

lispyclouds19:11:02

Java 19 Virtual Threads! My new favourite thing!

kwladyka19:11:21

19? Let’s say Java 17 max 🙂

kwladyka19:11:47

but you made me curious

lispyclouds19:11:07

I would use some form of java.util.CompletableFuture and have a list of them and invoke them all and wait for all of them

lispyclouds19:11:17

with virtual threads, id use structured concurrency. virtual threads are built exactly for this purpose:

(defn structured
  []
  (let [scope      (StructuredTaskScope$ShutdownOnFailure.)
        task1      (.fork scope
                          (fn []
                            (Thread/sleep 1000)
                            10))
        task2      (.fork scope
                          (fn []
                            (Thread/sleep 1000)
                            42))
        start-time (System/currentTimeMillis)
        _ (.join scope)
        _ (.throwIfFailed scope)
        end-time   (System/currentTimeMillis)]
    {:result  (* (.resultNow task1) (.resultNow task2))
     :time-ms (- end-time start-time)}))

lispyclouds19:11:50

all the tasks here wait in parallel and since its all IO bound in your case, they dont consume a whole thread each

kwladyka19:11:28

in my case this IO operations are actually pretty heavy. They copy thousands of millions of rows from CSV to postgresql. I am actually not sure how much CPU it needs. I didn’t profile this yet.

kwladyka19:11:53

this is hobby project….

kwladyka19:11:48

Why java.util.CompletableFuture instead of some of clojure solution like promise or pmap?

lispyclouds19:11:05

id say still the network and waiting time would dominate over the CPU time. is its all in a single postgres transaction?

kwladyka19:11:39

I don’t use transactions for this

kwladyka19:11:11

but yes, probably postgresql is a bottleneck

lispyclouds19:11:35

clojure future does give you a completablefutue. id not use pmap as this is a chain of sideeffects and pmap has laziness and chunking; not a good combo with side effects i think

kwladyka19:11:56

with doall of course

lispyclouds19:11:29

if its a lot of INSERTs highly recommend a transaction, both from reliability and perf reasons

kwladyka19:11:45

Can you say more about that?

kwladyka19:11:47

my idea was to have each functions to operate only on 1 table. So to 1 table will inserting only 1 function at a time.

kwladyka19:11:02

There will be no situation where many thread will inserting to 1 table

kwladyka19:11:13

Do I still need transaction to have good performance?

kwladyka19:11:49

My intuition saying me it is the best simple solution, but it can be harder, than I think 🙂

lispyclouds19:11:24

okay so whats the max level of concurrency could you posisbly have?

kwladyka19:11:00

I don’t know yet. This is a beginning of this project.

kwladyka19:11:06

right now 3

lispyclouds19:11:36

i was saying transactions as each insert is wrapped in a transaction if ones already not started and if theres 1000s of inserts, batching them all into on txn is faster

kwladyka19:11:57

But I think I will not cross 10 unless I will find a way to to make it faster and still keeping simple

kwladyka19:11:45

yes, I do 2000 row insert in 1 query

lispyclouds19:11:02

that should do it too

lispyclouds19:11:44

so its 3 tables and 3 big insert queries is what i understand?

kwladyka19:11:13

Do you have some experience in choosing the most performant number of rows in 1 insert query?

kwladyka19:11:30

expectation if it should be 100 / 2 000 / 20 000

kwladyka19:11:15

yes, right now 3 tables and each table has own feeder

kwladyka19:11:39

I don’t think inserting to 1 table from 10 threads will make if faster. Maybe even slower. But I can be wrong.

lispyclouds19:11:44

not sure if theres a right number here. depends on the network bandwidth and data marshalling efficiency

kwladyka19:11:25

Because if inserting to 1 table from 10 threads is faster, than inserting to 1 table from 1 thread, then I will do it in different way

lispyclouds19:11:06

yeah for me too i think being able to batch most into a single query/txn should be the most efficient then concurrent queries

Ben Sless19:11:26

Alternative approach - download the file, then import it from postgres

1
lispyclouds19:11:30

id say in this case since the question is about how much a query can fit rather than concurrency, try profiling each query first?

kwladyka19:11:52

> Alternative approach - download the file, then import it from postgres I have to modify it a little before import

kwladyka19:11:22

yes I will, but still the question about concurrency is valid

kwladyka20:11:00

I mean all of this options are valid to run functions parallel

kwladyka20:11:22

Just trying to think if I should prefer one on another for some reason

kwladyka20:11:35

overthinking probably ;)

Ben Sless20:11:52

Few other things to consider - batching your inserts, and doing everything in one transaction

Ben Sless20:11:06

but start with just trying to run it single threaded and see how it holds up before you parallelize

kwladyka20:11:17

I already did it

Ben Sless20:11:11

Did you time each section of execution?

kwladyka20:11:36

no, it is proof of concept stage right now heh

lispyclouds20:11:40

so the way id approach is if the follwoing is true: • 1 table per csv • all rows can fit into a single insert without choking newtork have a fn which takes in data and runs the big insert make this fn return a future have a list of these futures and call (run! deref futures)

Ben Sless20:11:06

I think the most important part before doing any sort of optimization is measurement. Should give you pretty good indication where you should focus your efforts

Ben Sless20:11:27

and when you start multithreading you run into other issues, such as bottlenecks and contention

lispyclouds20:11:45

big +1, id time each query and find the network or marshalling bottlenecks

kwladyka20:11:59

agree, the postgresql is side topic of this thread which became main topis heh

Ben Sless20:11:05

For example, if the files are large, you might be chocked on GC if you parallelize downloading the parsing them

kwladyka20:11:29

I do lazy parsing CSV

Ben Sless20:11:37

So I'd say it's beneficial to understand the characteristics of each part of your pipeline and their resource requirements

kwladyka20:11:38

otherwise OOM

Ben Sless20:11:00

That's good, but the downside is you hold on to all those delicious bytes you got off the network

Ben Sless20:11:30

so multiple files - more garbage, more OOM

Ben Sless20:11:58

Unless you stream the bytes in from the client

Ben Sless20:11:40

Which you can do with the builtin http client with LineConsumer (or something)

kwladyka20:11:47

heh of course not. This are ZIP files which I have to unarchive.

Ben Sless20:11:03

but at least you can stream that part

kwladyka20:11:48

@U7ERLH6JX why (run! deref futures) vs pmap or async or (Thread. ...). I am asking because I don’t have a stron opinion which one to use.

kwladyka20:11:47

I have this feeling (doall (pmap ...)) do all what I need and care about number of thread, but hmm just looking strange

Ben Sless20:11:07

the problem is that different problems should be bound by different numbers of threads

lispyclouds20:11:19

im always a bit iffy against pmap and side effects as the paralleslism is uncontrolled and you never know

Ben Sless20:11:33

e.g. downloading IO bound, unzip and process is CPU bound and ingest is postgres bound

Ben Sless20:11:51

I would use three core async pipelines with different parallelism here

kwladyka20:11:15

yes, but from I saw usually when software developers try to control number of threads by themselves it perform worst, than pmap. And machine can change in any moment, the CPU can be better / worst.

lispyclouds20:11:58

so we dont want to control the threads per se but the number of things in parallel

kwladyka20:11:00

but with (run! deref futures) it is as uncontrolled as pmap right?

lispyclouds20:11:23

its controlled by the count of futures

lispyclouds20:11:42

same as what Ben is saying with pipelines

kwladyka20:11:16

it will be the same situation with pmap, because [f1 f2 f3 ....] will control thread by number of functions

kwladyka20:11:23

almost the same like number of features

kwladyka20:11:27

but here are functions

kwladyka20:11:56

> I would use three core async pipelines with different parallelism here Can be, but not a overkill?

kwladyka20:11:01

and again I have to control number of parallel N after change a machine. pmap will change it by automate.

kwladyka20:11:23

well I think I will start from whatever. It shouldn’t make any difference in performance.

kwladyka20:11:30

Thank you for your input

Ben Sless20:11:30

> Can be, but not a overkill? Possible, but I'm used to it

lispyclouds20:11:48

well this one from Ben themselves tries to address similar feelings i have for pmapping side effects 😄 https://bsless.github.io/mapping-parallel-side-effects/

lispyclouds20:11:07

the But Why Not pmap? section

kwladyka20:11:14

yeah the pmap is specific… although in work 1 software developer were trying to do async to increase performance for a long time… After that I removed all this code and I used pmap achieving better performance.

kwladyka20:11:45

so while this is true pmap do things by automate and you don’t have control of it does it good

kwladyka20:11:01

and usually this is not a problem you can’t control it

kwladyka20:11:08

not always of course

lispyclouds20:11:38

> After that I removed all this code and I used pmap achieving better performance. probably the bottleneck wasnt in the code but outside like network or io

kwladyka20:11:00

async is just hard and complex

lispyclouds20:11:41

well, nice plug to my first message: java 19 virtual threads, solution of all of this 😛

👍 1
lispyclouds20:11:00

What Ben is talking about as async pipelines is formally done in the structured example i showed

kwladyka20:11:20

but I have also another hobby project which is for synchronising bookeeping + warehouse + estore and there async is have to to strictly control side effects

kwladyka20:11:50

I think while it is not the case (and usually is not) pmap if perfect

lispyclouds20:11:44

to overuse our favourite quote: pmap here is easy not simple.

kwladyka20:11:19

hmm I see it as easy and simple

kwladyka20:11:11

As long as software developer understand what pmap does (side effects) it is safe to use

lispyclouds20:11:19

not simple from the fact that its hiding a lot of machinery and isnt decoupled enough for reasons like error handling etc

kwladyka20:11:18

heh I see it simple exactly because it does all this machinery under the hood and as a software developer you don’t have to write it yourself

lispyclouds20:11:39

thats the easy bit there

kwladyka20:11:55

yes, but also I can do things in 1 line which make it simple

seancorfield20:11:05

Exactly. pmap is easy but it is not "simple" in the Clojure definition of things -- you are conflating them.

seancorfield20:11:17

pmap is easy but it is nearly always the wrong solution for production code. Learn to use the underlying platform's executors / thread pool stuff where you have a lot more control over individual pieces -- and you can tune them as a result of profiling/benchmarking.

1
kwladyka20:11:56

Hmm when I can do a thing in 1 line it is simple for me. What make it complex in your opinion?

seancorfield20:11:45

That has been explained repeatedly in this thread. You've been given a lot of good advice here but you just don't seem to want it...

kwladyka20:11:58

> where you have a lot more control over individual pieces Agree, but this is not always needed.

seancorfield20:11:42

pmap = laziness + lack of control over concurrency. You can get both too much concurrency in some situations and not enough in others, and you're relying on a whole bunch of machinery to maybe do the right thing (or maybe not). And your use case is about concurrency over two or three completely different processes, only one of which is likely to be CPU bound. They need different approaches.

kwladyka20:11:00

oh I know why maybe we name pmap differently. It is complex function but it is simple to use. That is what I was meaning.

seancorfield20:11:33

"It is complex function but it is easy to use." -- fixed that for you.

1
kwladyka20:11:11

so far I didn’t see async which was simpler, than pmap in any way. But maybe I didn’t see good async project.

lispyclouds20:11:19

just as a summary which helps me: • pmap: okay for parallel unordered CPU bound tasks. hardly the case in a lot of prod code. not to be used for IO • everything else: a specific tool for a specific problem

1
robertfw20:11:37

@U0WL6FA77 In case you are wondering why there is some specificity being used here between "simple" and "easy", the following talk by Rich Hickey is considered one of the "core concepts" of Clojure https://youtu.be/LKtk3HCgTa8

lispyclouds20:11:08

writing correct async code and reasoning about it is very very hard. no doubts on that one. hence theres a lot of effort to address it, specially on the JVM side

kwladyka20:11:08

let’s reverse it: show me simpler code for async, than pmap but NOT for a thing which you have to strictly control side effects like for example bookkeeping + warehouse + estore.

seancorfield20:11:02

But your measure of "simpler code" is that it is easier for you to understand -- instead of learning how to do this properly.

1
kwladyka20:11:31

no, it is the measure of things which I have to keep in my head during coding and maintenance this code later

kwladyka20:11:13

like “what is happening in this code” make it complex too (but not in the sense of core functions which I don’t know, but just a complex code with complex flow)

seancorfield20:11:16

You asked for advice. We gave you advice. If you don't want to take that advice and learn how to use the things we're suggesting, that's up to you. When you have the choice between Thing A that you already know how to use and Thing B which you don't, A is always going to seem easier. If you learn how to use B, it becomes easier for the future. I'm dropping out of this thread now.

kwladyka20:11:52

sure, thank you

kwladyka21:11:49

edit: (deleted, misread)

skylize21:11:40

Clojurists mean very specific things when they use the terms "simple" and "easy". Most are happy to explain the distinction, as several people have already done. And if those explanations weren't clear enough for you, then watch the video @UG00LE5TN linked above. Trying to argue their meanings does not help you or anybody else, because it is shorthand used unanimously by the community for important concepts at the core of Clojure. Just learn it like you learn the name of a function, so you can get the most out of people trying to help you. There is nothing wrong with choosing something "easy" for expediency. Just realize that you are likely setting yourself up for future headaches, because you didn't choose "simple".

kwladyka21:11:09

@U90R0EPHA To cut the topic off: Can you write the definition in a few sentences?

skylize21:11:05

"Simple" describes a low level of actual complexity of a thing. "Easy" describes difficulty in getting a thing to do exactly the thing is was designed for. The two are not necessarily mutually exclusive. But easy is often the result of complex infrastructure built up underneath. So as soon as you want to do something ever-so-slightly different from the original intent, that complexity rises in front of you putting up roadblocks.

👍 1
kwladyka21:11:17

so what is complex in pmap for use case when you don’t have to control side effects order?

kwladyka21:11:26

by your definition

skylize21:11:51

I'm basically a beginner to Clojure as well. I have not had any reason yet to use pmap, so cannot really comment. But @U04V70XH6 is one of the most experienced Clojure devs you will ever get to talk to. So I suggest paying close attention to his take on it. > and you're relying on a whole bunch of machinery to maybe do the right thing (or maybe not)

kwladyka21:11:55

Agree, @U04V70XH6 is a very good developer. I am not questioning it.

seancorfield21:11:08

Stop @-ing me, both of you!

👍 1
seancorfield21:11:21

I said I was dropping out of this thread -- stop dragging me back in.

kwladyka21:11:44

@U90R0EPHA I agree with that, but now one could give me any example or explanation what is complex in pmap for the use case where you don’t need to control side effects order and juice 101% of performance. That make a discussion less educative.

kwladyka21:11:14

with all other cases I totally agree, it is even almost impossible to do things with pmap.

kwladyka21:11:20

So in my feeling it is more about repeating common trues, than really focus on use case. But maybe I am the one who misunderstand what they wanted to say.

kwladyka21:11:02

All in all I think it is more about misunderstanding both sides.

skylize21:11:47

I got the distinct impression everyone was very focused on trying to understand your exact usecase, because the answer to your original question is "it depends".

skylize21:11:58

And it sounds like you're pretty happy with pmap for now, so go for it. But, I guess you've been warned. Probably should bookmark this thread for when your usecase outgrows the "easy" of pmap. 😉

kwladyka21:11:12

yeah, it is even too early for this code to optimise it in that sense to get the best performance. it is proof of work and even I don’t know what will be final need for it.

kwladyka21:11:04

I think the main argue is: in my opinion pmap is simple or complex depends on use case, while all others opinion is pmap is always complex.

kwladyka21:11:05

But again definitely not for all use cases. I hope I made it clear. For many uses cases pmap is a very bad choice and I would never use it.

didibus04:11:36

If it's IO, number of processors doesn't matter, so definitely you shouldn't be using pmap. You can just use a bunch of futures on their own, unless you have like hundreds+ of them you're trying to do concurrently, then you probably want a queue, some executor services are good here or core.async.

didibus04:11:39

But postgress itself will have a limit. Like how many concurent inserts to postress you think your DB can handle? Parallelizing on your side won't help you go faster then the single writer which is Postgress. In fact, you might slow it down further if you overload it. Once you figure that out, and I expect it's not that much, you can experiment with just futures. Like try with 30 futures, and see the load on postgress, and if it's too high go down to 20, or if not high enough go.uo to 40.

didibus04:11:53

Basically, think about it, Postgress is like a highway entrance, trying to send too many cars to enter it at ounce you'll just get congestion. You want to saturate the lanes but not try to jam more cars then there are lanes.

kwladyka07:11:26

we mix topics and there is misunderstanding here. Let’s make the thread end.

agorgl20:11:03

Hello! How can I var quote (`#'`) a symbol without specifying full path to the current name space? E.g. I define a pedestal service with ::http/routes #(deref #'routes) in sample.service.core namespace. The routes symbol is def'd in the sample.service.core namespace. When running the service through -main everything works correctly, because -main is defined in sample.service.core namespace, but when running it in dev mode the service is launched through user ns and as expected the routes binding does not exist there. So I should either change above to ::http/routes #(deref #'sample.service.core/routes)` or to something less verbose / more transferable. Any ideas?

p-himik20:11:13

A namespace alias?

agorgl20:11:53

Is there a self namespace alias or something?

agorgl20:11:02

Like ::http/routes #(deref #'self/routes)` ?

hiredman20:11:13

just do the same thing you would do if you weren't using var-quote

hiredman20:11:40

the name in var-quote is resolved the same way as the name without var-quote

p-himik20:11:04

You can't alias the "self" namespace. But you can move that code to a common namespace and alias it instead.

agorgl20:11:33

You can see that the server.clj example uses an alias to service namespace

hiredman20:11:38

user=> clojure.reflect/reflect
Syntax error (ClassNotFoundException) compiling at (REPL:0:0).
clojure.reflect
user=> #'clojure.reflect/reflect
Syntax error compiling var at (REPL:0:0).
Unable to resolve var: clojure.reflect/reflect in this context
user=> (require '[clojure.reflect :as r])
nil
user=> clojure.reflect/reflect
#object[clojure.reflect$reflect 0x12a160c2 "clojure.reflect$reflect@12a160c2"]
user=> #'clojure.reflect/reflect
#'clojure.reflect/reflect
user=> #'r/reflect
#'clojure.reflect/reflect
user=>

agorgl20:11:17

It just seems dumb to do:

(ns sample.service.core
  (:require [io.pedestal.http :as http]
            [sample.service.core :as core]))

agorgl20:11:41

In order to do: ::http/routes #(deref #'core/routes)

p-himik20:11:30

Why do you even need to have the same exact code in that lambda, character for character? Let one place have core/routes and the other just routes if you don't want to move routes to a different ns.

agorgl20:11:11

First of all is just an example that I bumped into and using that I'm trying to have a more deep understanding of clojure namespaces and symbols 🙂 The whole POC is this:

(ns sample.service.core
  (:gen-class) ; for -main method in uberjar
  (:require [io.pedestal.http :as http]))

(defn hello-world
  [request]
  (let [name (get-in request [:params :name] "World")]
    {:status 200 :body (str "Hello " name "!\n")}))

(def routes
  #{["/greet" :get `hello-world]})

(def service {:env                 :prod
              ::http/routes        routes
              ::http/resource-path "/public"
              ::http/type          :jetty
              ::http/port          8080})

;; This is an adapted service map, that can be started and stopped
;; From the REPL you can call http/start and http/stop on this service
(defonce runnable-service (http/create-server service))

(defn run-dev
  "The entry point for dev"
  [& _]
  (println "\nCreating your [DEV] server...")
  (-> service ;; start with production configuration
      (merge {:env :dev
              ;; do not block thread that starts web server
              ::http/join? false
              ;; Routes can be a function that resolve routes,
              ;;  we can use this to set the routes to be reloadable
              ::http/routes #(deref #'routes)
              ;; all origins are allowed in dev mode
              ::http/allowed-origins {:creds true :allowed-origins (constantly true)}})
      ;; Wire up interceptor chains
      http/default-interceptors
      http/dev-interceptors
      http/create-server
      http/start))

(defn -main
  "The entry point"
  [& _]
  (println "\nCreating your server...")
  (http/start runnable-service))

agorgl20:11:05

In this POC, the run-dev function uses the deref #' trick to make routes reloadable in pedestal. The problem is that this does not work, because if you run the run-dev function from a dev repl that starts in the user namespace it tries to resolve the routes symbol in the user ns as user/routes, but I want it to resolve to the sample.service.core ns as sample.service.core/routes

agorgl20:11:14

The obvious answer is to change #(deref #'routes) to #(deref #'sample.service.core/routes) I was just wondering if there is a better way (given I want to keep everything in the same namespace)

agorgl20:11:40

I do not intent to keep it all that way, its mostly for learning purposes

hiredman20:11:39

if you want to use code is core, why is dump to require core?

agorgl20:11:15

Sorry the require core is mistype from before

hiredman20:11:33

#(deref #'core/routes) is equivalent to #(do core/routes)

hiredman20:11:30

or (fn [] core/routes)

agorgl20:11:42

Ok still, how can I avoid core self reference/alias

hiredman20:11:21

once you start wrapping it in a function, you don't need var quote to ensure you get an updated value

hiredman20:11:09

not sure what you mean by self ref/alias

hiredman20:11:10

are you just trying to avoid a cyclic dependency in your namespaces?

agorgl21:11:13

Sorry in the end I may not understand the problem correctly

agorgl21:11:28

I'm trying to solve an exception when running the run-dev function

hiredman21:11:38

what is the exception?

agorgl21:11:52

That seems to be resolved If I replace ::http/routes #(deref #'routes) with ::http/routes routes

agorgl21:11:58

Gimme a sec

hiredman21:11:36

like, I haven't used pedastal, but they might only allow a single layer of function indirection

hiredman21:11:02

so if routes is a function, and your function returns its value, then you have a function returning a function instead of a function returning routes

hiredman21:11:40

sure, but I don't know what edits you might have made

hiredman21:11:28

but what is the exception you are getting?

agorgl21:11:17

Exception:

Error processing request!
Exception:

clojure.lang.ExceptionInfo: java.lang.NullPointerException in Interceptor :io.pedestal.http.route/router - 
{:execution-id 1, :stage :enter, :interceptor :io.pedestal.http.route/router, :exception-type :java.lang.NullPointerException, :exception #error {
 :cause nil
 :via
 [{:type java.lang.NullPointerException
   :message nil
   :at [java.util.regex.Matcher getTextLength "Matcher.java" 1770]}]
 :trace
 [[java.util.regex.Matcher getTextLength "Matcher.java" 1770]
  [java.util.regex.Matcher reset "Matcher.java" 416]
  [java.util.regex.Matcher <init> "Matcher.java" 253]
  [java.util.regex.Pattern matcher "Pattern.java" 1133]
  [java.util.regex.Pattern split "Pattern.java" 1261]
  [java.util.regex.Pattern split "Pattern.java" 1334]
  [clojure.string$split invokeStatic "string.clj" 225]
  [clojure.string$split invoke "string.clj" 219]
  [io.pedestal.http.route.prefix_tree$partition_wilds invokeStatic "prefix_tree.clj" 52]
  [io.pedestal.http.route.prefix_tree$partition_wilds invoke "prefix_tree.clj" 47]
  [io.pedestal.http.route.prefix_tree$contains_wilds_QMARK_ invokeStatic "prefix_tree.clj" 84]
  [io.pedestal.http.route.prefix_tree$contains_wilds_QMARK_ invoke "prefix_tree.clj" 80]
  [clojure.core$some invokeStatic "core.clj" 2718]
  [clojure.core$some invoke "core.clj" 2709]
  [io.pedestal.http.route.map_tree$router invokeStatic "map_tree.clj" 55]
  [io.pedestal.http.route.map_tree$router invoke "map_tree.clj" 51]
  [io.pedestal.http.route$eval11808$fn__11809$fn__11810 invoke "route.clj" 473]
  [io.pedestal.interceptor.chain$try_f invokeStatic "chain.clj" 54]
  [io.pedestal.interceptor.chain$try_f invoke "chain.clj" 44]
  [io.pedestal.interceptor.chain$process_all_with_binding invokeStatic "chain.clj" 171]
  [io.pedestal.interceptor.chain$process_all_with_binding invoke "chain.clj" 146]
  [io.pedestal.interceptor.chain$process_all$fn__10681 invoke "chain.clj" 188]
  [clojure.lang.AFn applyToHelper "AFn.java" 152]
  [clojure.lang.AFn applyTo "AFn.java" 144]
  [clojure.core$apply invokeStatic "core.clj" 667]
  [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1990]
  [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1990]
  [clojure.lang.RestFn invoke "RestFn.java" 425]
  [io.pedestal.interceptor.chain$process_all invokeStatic "chain.clj" 186]
  [io.pedestal.interceptor.chain$process_all invoke "chain.clj" 182]
  [io.pedestal.interceptor.chain$enter_all invokeStatic "chain.clj" 235]
  [io.pedestal.interceptor.chain$enter_all invoke "chain.clj" 229]
  [io.pedestal.interceptor.chain$execute invokeStatic "chain.clj" 379]
  [io.pedestal.interceptor.chain$execute invoke "chain.clj" 352]
  [io.pedestal.interceptor.chain$execute invokeStatic "chain.clj" 389]
  [io.pedestal.interceptor.chain$execute invoke "chain.clj" 352]
  [io.pedestal.http.impl.servlet_interceptor$interceptor_service_fn$fn__15075 invoke "servlet_interceptor.clj" 351]
  [io.pedestal.http.servlet.FnServlet service "servlet.clj" 28]
  [org.eclipse.jetty.servlet.ServletHolder handle "ServletHolder.java" 799]
  [org.eclipse.jetty.servlet.ServletHandler doHandle "ServletHandler.java" 554]
  [org.eclipse.jetty.server.handler.ScopedHandler nextHandle "ScopedHandler.java" 233]
  [org.eclipse.jetty.server.handler.ContextHandler doHandle "ContextHandler.java" 1440]
  [org.eclipse.jetty.server.handler.ScopedHandler nextScope "ScopedHandler.java" 188]
  [org.eclipse.jetty.servlet.ServletHandler doScope "ServletHandler.java" 505]
  [org.eclipse.jetty.server.handler.ScopedHandler nextScope "ScopedHandler.java" 186]
  [org.eclipse.jetty.server.handler.ContextHandler doScope "ContextHandler.java" 1355]
  [org.eclipse.jetty.server.handler.ScopedHandler handle "ScopedHandler.java" 141]
  [org.eclipse.jetty.server.handler.HandlerWrapper handle "HandlerWrapper.java" 127]
  [org.eclipse.jetty.server.Server handle "Server.java" 516]
  [org.eclipse.jetty.server.HttpChannel lambda$handle$1 "HttpChannel.java" 487]
  [org.eclipse.jetty.server.HttpChannel dispatch "HttpChannel.java" 732]
  [org.eclipse.jetty.server.HttpChannel handle "HttpChannel.java" 479]
  [org.eclipse.jetty.server.HttpConnection onFillable "HttpConnection.java" 277]
  [org.eclipse.jetty.io.AbstractConnection$ReadCallback succeeded "AbstractConnection.java" 311]
  [org.eclipse.jetty.io.FillInterest fillable "FillInterest.java" 105]
  [org.eclipse.jetty.io.ChannelEndPoint$1 run "ChannelEndPoint.java" 104]
  [org.eclipse.jetty.util.thread.QueuedThreadPool runJob "QueuedThreadPool.java" 883]
  [org.eclipse.jetty.util.thread.QueuedThreadPool$Runner run "QueuedThreadPool.java" 1034]
  [java.lang.Thread run "Thread.java" 829]]}}
 at io.pedestal.interceptor.chain$throwable__GT_ex_info.invokeStatic (chain.clj:35)
    io.pedestal.interceptor.chain$throwable__GT_ex_info.invoke (chain.clj:32)
    io.pedestal.interceptor.chain$try_f.invokeStatic (chain.clj:57)
    io.pedestal.interceptor.chain$try_f.invoke (chain.clj:44)
    io.pedestal.interceptor.chain$process_all_with_binding.invokeStatic (chain.clj:171)
    io.pedestal.interceptor.chain$process_all_with_binding.invoke (chain.clj:146)
    io.pedestal.interceptor.chain$process_all$fn__10681.invoke (chain.clj:188)
    clojure.lang.AFn.applyToHelper (AFn.java:152)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$with_bindings_STAR_.invokeStatic (core.clj:1990)
    clojure.core$with_bindings_STAR_.doInvoke (core.clj:1990)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    io.pedestal.interceptor.chain$process_all.invokeStatic (chain.clj:186)
    io.pedestal.interceptor.chain$process_all.invoke (chain.clj:182)
    io.pedestal.interceptor.chain$enter_all.invokeStatic (chain.clj:235)
    io.pedestal.interceptor.chain$enter_all.invoke (chain.clj:229)
    io.pedestal.interceptor.chain$execute.invokeStatic (chain.clj:379)
    io.pedestal.interceptor.chain$execute.invoke (chain.clj:352)
...

agorgl21:11:32

...
    io.pedestal.interceptor.chain$execute.invokeStatic (chain.clj:389)
    io.pedestal.interceptor.chain$execute.invoke (chain.clj:352)
    io.pedestal.http.impl.servlet_interceptor$interceptor_service_fn$fn__15075.invoke (servlet_interceptor.clj:351)
    io.pedestal.http.servlet.FnServlet.service (servlet.clj:28)
    org.eclipse.jetty.servlet.ServletHolder.handle (ServletHolder.java:799)
    org.eclipse.jetty.servlet.ServletHandler.doHandle (ServletHandler.java:554)
    org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:233)
    org.eclipse.jetty.server.handler.ContextHandler.doHandle (ContextHandler.java:1440)
    org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:188)
    org.eclipse.jetty.servlet.ServletHandler.doScope (ServletHandler.java:505)
    org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:186)
    org.eclipse.jetty.server.handler.ContextHandler.doScope (ContextHandler.java:1355)
    org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:141)
    org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:127)
    org.eclipse.jetty.server.Server.handle (Server.java:516)
    org.eclipse.jetty.server.HttpChannel.lambda$handle$1 (HttpChannel.java:487)
    org.eclipse.jetty.server.HttpChannel.dispatch (HttpChannel.java:732)
    org.eclipse.jetty.server.HttpChannel.handle (HttpChannel.java:479)
    org.eclipse.jetty.server.HttpConnection.onFillable (HttpConnection.java:277)
    org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded (AbstractConnection.java:311)
    org.eclipse.jetty.io.FillInterest.fillable (FillInterest.java:105)
    org.eclipse.jetty.io.ChannelEndPoint$1.run (ChannelEndPoint.java:104)
    org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:883)
    org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run (QueuedThreadPool.java:1034)
    java.lang.Thread.run (Thread.java:829)
Caused by: java.lang.NullPointerException: null
 at java.util.regex.Matcher.getTextLength (Matcher.java:1770)
    java.util.regex.Matcher.reset (Matcher.java:416)
    java.util.regex.Matcher.<init> (Matcher.java:253)
    java.util.regex.Pattern.matcher (Pattern.java:1133)
    java.util.regex.Pattern.split (Pattern.java:1261)
    java.util.regex.Pattern.split (Pattern.java:1334)
    clojure.string$split.invokeStatic (string.clj:225)
    clojure.string$split.invoke (string.clj:219)
    io.pedestal.http.route.prefix_tree$partition_wilds.invokeStatic (prefix_tree.clj:52)
    io.pedestal.http.route.prefix_tree$partition_wilds.invoke (prefix_tree.clj:47)
    io.pedestal.http.route.prefix_tree$contains_wilds_QMARK_.invokeStatic (prefix_tree.clj:84)
    io.pedestal.http.route.prefix_tree$contains_wilds_QMARK_.invoke (prefix_tree.clj:80)
    clojure.core$some.invokeStatic (core.clj:2718)
    clojure.core$some.invoke (core.clj:2709)
    io.pedestal.http.route.map_tree$router.invokeStatic (map_tree.clj:55)
    io.pedestal.http.route.map_tree$router.invoke (map_tree.clj:51)
    io.pedestal.http.route$eval11808$fn__11809$fn__11810.invoke (route.clj:473)
    io.pedestal.interceptor.chain$try_f.invokeStatic (chain.clj:54)
    io.pedestal.interceptor.chain$try_f.invoke (chain.clj:44)
    io.pedestal.interceptor.chain$process_all_with_binding.invokeStatic (chain.clj:171)
    io.pedestal.interceptor.chain$process_all_with_binding.invoke (chain.clj:146)
    io.pedestal.interceptor.chain$process_all$fn__10681.invoke (chain.clj:188)
    clojure.lang.AFn.applyToHelper (AFn.java:152)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$with_bindings_STAR_.invokeStatic (core.clj:1990)
    clojure.core$with_bindings_STAR_.doInvoke (core.clj:1990)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    io.pedestal.interceptor.chain$process_all.invokeStatic (chain.clj:186)
    io.pedestal.interceptor.chain$process_all.invoke (chain.clj:182)
    io.pedestal.interceptor.chain$enter_all.invokeStatic (chain.clj:235)
    io.pedestal.interceptor.chain$enter_all.invoke (chain.clj:229)
    io.pedestal.interceptor.chain$execute.invokeStatic (chain.clj:379)
    io.pedestal.interceptor.chain$execute.invoke (chain.clj:352)
    io.pedestal.interceptor.chain$execute.invokeStatic (chain.clj:389)
    io.pedestal.interceptor.chain$execute.invoke (chain.clj:352)
    io.pedestal.http.impl.servlet_interceptor$interceptor_service_fn$fn__15075.invoke (servlet_interceptor.clj:351)
    io.pedestal.http.servlet.FnServlet.service (servlet.clj:28)
    org.eclipse.jetty.servlet.ServletHolder.handle (ServletHolder.java:799)
    org.eclipse.jetty.servlet.ServletHandler.doHandle (ServletHandler.java:554)
    org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:233)
    org.eclipse.jetty.server.handler.ContextHandler.doHandle (ContextHandler.java:1440)
    org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:188)
    org.eclipse.jetty.servlet.ServletHandler.doScope (ServletHandler.java:505)
    org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:186)
    org.eclipse.jetty.server.handler.ContextHandler.doScope (ContextHandler.java:1355)
    org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:141)
    org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:127)
    org.eclipse.jetty.server.Server.handle (Server.java:516)
    org.eclipse.jetty.server.HttpChannel.lambda$handle$1 (HttpChannel.java:487)
    org.eclipse.jetty.server.HttpChannel.dispatch (HttpChannel.java:732)
    org.eclipse.jetty.server.HttpChannel.handle (HttpChannel.java:479)
    org.eclipse.jetty.server.HttpConnection.onFillable (HttpConnection.java:277)
    org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded (AbstractConnection.java:311)
    org.eclipse.jetty.io.FillInterest.fillable (FillInterest.java:105)
    org.eclipse.jetty.io.ChannelEndPoint$1.run (ChannelEndPoint.java:104)
    org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:883)
    org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run (QueuedThreadPool.java:1034)
    java.lang.Thread.run (Thread.java:829)

agorgl21:11:42

Context:

{:response nil,
 :cors-headers
 {"Access-Control-Allow-Origin" "",
  "Access-Control-Allow-Credentials" "true"},
 :io.pedestal.interceptor.chain/stack
 ({:name :io.pedestal.http.impl.servlet-interceptor/ring-response,
   :enter nil,
   :leave
   #function[io.pedestal.http.impl.servlet-interceptor/leave-ring-response],
   :error
   #function[io.pedestal.http.impl.servlet-interceptor/error-ring-response]}
  {:name :io.pedestal.http.impl.servlet-interceptor/stylobate,
   :enter
   #function[io.pedestal.http.impl.servlet-interceptor/enter-stylobate],
   :leave
   #function[io.pedestal.http.impl.servlet-interceptor/leave-stylobate],
   :error
   #function[io.pedestal.http.impl.servlet-interceptor/error-stylobate]}
  {:name
   :io.pedestal.http.impl.servlet-interceptor/terminator-injector,
   :enter #function[io.pedestal.interceptor.helpers/before/fn--10888],
   :leave nil,
   :error nil}),
 :request
 {:protocol "HTTP/1.1",
  :async-supported? true,
  :remote-addr "127.0.0.1",
  :servlet-response
  #object[org.eclipse.jetty.server.Response 0x193edc94 "HTTP/1.1 200 \nDate: Wed, 02 Nov 2022 21:03:34 GMT\r\n\r\n"],
  :servlet
  #object[io.pedestal.http.servlet.FnServlet 0x4c3dbb8c "io.pedestal.http.servlet.FnServlet@4c3dbb8c"],
  :headers
  {"origin" "",
   "sec-fetch-site" "none",
   "sec-ch-ua-mobile" "?0",
   "host" "localhost:8080",
   "user-agent"
   "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
   "cookie"
   "visited-index=true; admin-auth=Basic%20YWRtaW46OUxjdjNSOUVxMkZ5ak02OHVaVm4h; initial-tab=; current-applet=23FA4143-22B4-49D7-BDC5-3ED54EDF69CB; wguser=anonymous; connect.sid=s%3AWi8UbBRQ7OA_MhqSjhDDAu9Bj4WHSVnG.4lqLECNJOlRXUOhkR7wNCcApZ13HieNY75Ld7ytmJM0",
   "sec-fetch-user" "?1",
   "sec-ch-ua" "\"Chromium\";v=\"107\", \"Not=A?Brand\";v=\"24\"",
   "sec-ch-ua-platform" "\"Linux\"",
   "connection" "keep-alive",
   "upgrade-insecure-requests" "1",
   "accept"
   "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
   "accept-language" "en-US,en;q=0.9",
   "sec-fetch-dest" "document",
   "accept-encoding" "gzip, deflate, br",
   "sec-fetch-mode" "navigate"},
  :server-port 8080,
  :servlet-request
  #object[org.eclipse.jetty.server.Request 0x252fc77f "Request(GET //localhost:8080/)@252fc77f"],
  :path-info "/",
  :uri "/",
  :server-name "localhost",
  :query-string nil,
  :body
  #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x409c2b3b "HttpInputOverHTTP@409c2b3b[c=0,q=0,[0]=null,s=STREAM]"],
  :scheme :http,
  :request-method :get,
  :context-path ""},
 :enter-async
 [#function[io.pedestal.http.impl.servlet-interceptor/start-servlet-async]],
 :io.pedestal.interceptor.chain/terminators
 (#function[io.pedestal.http.impl.servlet-interceptor/terminator-inject/fn--15050]),
 :servlet-response
 #object[org.eclipse.jetty.server.Response 0x193edc94 "HTTP/1.1 200 \nDate: Wed, 02 Nov 2022 21:03:34 GMT\r\n\r\n"],
 :servlet
 #object[io.pedestal.http.servlet.FnServlet 0x4c3dbb8c "io.pedestal.http.servlet.FnServlet@4c3dbb8c"],
 :servlet-request
 #object[org.eclipse.jetty.server.Request 0x252fc77f "Request(GET //localhost:8080/)@252fc77f"],
 :io.pedestal.interceptor.chain/execution-id 1,
 :servlet-config
 #object[org.eclipse.jetty.servlet.ServletHolder$Config 0x5753cf44 "org.eclipse.jetty.servlet.ServletHolder$Config@5753cf44"],
 :async?
 #function[io.pedestal.http.impl.servlet-interceptor/servlet-async?]}

agorgl21:11:02

(broke it to 3 messages in order to fit)

hiredman21:11:19

are you in a fresh repl?

hiredman21:11:19

what is the value of routes?

agorgl21:11:56

Ah I just found out it must be some problem with the hello world example in the pedestal repo

agorgl21:11:12

It seems that other examples wrap it in route/expand-routes

agorgl21:11:39

I'm reporting this upstream, sorry for your time

hiredman21:11:07

#(route/expand-routes (deref #'service/routes)) is equivalent to #(route/expand-routes service/routes) if you have direct linking off, which if you are redefining things you likely want off anyway

hiredman21:11:39

and I think it is equivalent in the direct linking case too

hiredman21:11:29

(direct linking only effects direct function calls, and that is not one)

agorgl21:11:51

Thanks for your help, I'll look more into it!

agorgl21:11:05

Thanks also for your patience towards a clojure newbie!