Fork me on GitHub
#clojure
<
2023-08-07
>
joshcho05:08:12

macroexpand-1 is useful but gets quite unreadable with all the namespaces in qualified symbols. What's the best way to remove the namespaces from all the symbols? I tried using prewalk and remove-ns but didn't quite work.

tomd07:08:02

qualified-symbol? and name are probably what you want

tomd07:08:21

e.g.

(clojure.walk/prewalk (fn [x] (if (qualified-symbol? x)
                                (symbol (name x))
                                x))
                      (macroexpand-1 '(cond 1 2 3 4 5 6)))
;; => (if 1 2 (cond 3 4 5 6))

👍 2
mx200008:08:53

I have been using the following function a lot:

(defn set-var-root [v value]
  (alter-var-root v (constantly value)))
And I have seen this pattern a lot in other code too. Why is it not in clojure.core? Might it be a good addition and if not, why not?

oyakushev08:08:54

You can use (.bindRoot v value)

👀 4
mx200009:08:23

.bindRoot is not in the public API, which also makes me ask why? It might not work in clojurescript Probably I will ask in http://ask.clojure.org, thank you

oyakushev09:08:25

Most likely because the public API encourages you to not lose the previous value of the var.

phill09:08:42

I have used that pattern to set Clojure's own global vars. But as for my own programming, I avoid globals altogether.

mx200009:08:22

I am trying to organise the state for the game engine I am developing and I am going a bit mad now , I might even make it purely Functional. But I am not sure to pass around for example 'input' everywhere and then

(input/key-pressed? input :x)
Instead of just this with global state:
(input/key-pressed? :x)

vemv10:08:25

vars are not meant to keep global state for something like a game. (Especially for a game? They tend to be concurrent) A (def input (atom ,,,)) is a lightweight, sensible choice. Although it's not perfect for testability, you might want to check out Component/Integrant if you haven't - you can place the atoms there, and have a nice dev/test system separation (and multiple test envs, maybe exercised in parallel)

mx200010:08:16

Excuse me, I should have mentioned that this is only the application-context-state and not the state of the game-world-objects. The variables like asset-manager, input, graphics-context get created only once on application start. So I am not using atoms here but vars. Only in dev-mode I am restarting the application while keeping the JVM and so I am using set-var-root. I am actually using https://github.com/damn/x.x component system, which I think is more flexible, I can extend even namespaces to system behaviour.

Joshua Suskalo15:08:01

The point here is that anything that changes over the course of a program should not be represented by the root binding of vars.

Joshua Suskalo15:08:56

atoms are the mechanism by which you implement change over time, and global atoms are acceptable. Instead of a global var that you alter, you should use a global var that binds to an atom which you swap!

Joshua Suskalo15:08:08

Rebinding vars during dev time is obviously ok, which it seems that this is probably the primary usecase you have for it.

Joshua Suskalo15:08:24

But in most of those cases a set-var-root! is unnecessary as you can simply change the definition and re-evaluate it.

mx200015:08:14

Yes these variables live with the application window and only get reset during development reloading

Joshua Suskalo15:08:52

I wouldn't really recommend having the application window be initialized as a def either, to be honest.

Joshua Suskalo15:08:06

Loading the application code should not start the application.

phill15:08:57

re "But I am not sure to pass around for example 'input' everywhere...", you will have a lot more flexibility if you do pass things around instead of doing "place-oriented" programming. If there is a genre of functions that should examine 'input' and you don't want to see 'input' everywhere, you can use a macro to define those functions...

mx200015:08:29

Those variables are static variables from java land which get initialised after the application window (lwjgl3&glfw) has been created. I am only putting a convinience var so I don't have to pass it around.

mx200015:08:55

So I can use 'input' instead of com.badlogic.gdx.Gdx/input

Joshua Suskalo15:08:07

That's a reasonable usecase.

mx200015:08:48

Also it gives me tagging for reflection handling

Joshua Suskalo16:08:50

Ah, I understand a bit better now.

Joshua Suskalo16:08:54

looking in at the code.

Joshua Suskalo16:08:59

I don't recommend using vars this way.

Joshua Suskalo16:08:03

I recommend using dynamic vars.

Joshua Suskalo16:08:34

For this particular case you could probably argue for doing it the way you are, but using dynamic vars would be more the "clojure way".

mx200016:08:31

That's interesting it might even work, but actually not be much difference to the library user. I think I am overthinking it sometimes. If nobody sees that vars or atoms or dynamic vars are used does it matter if it's doing the same thing ?

Joshua Suskalo16:08:56

Yes, to the library user there would not be significant difference. This is a question of idioms, not API.

mx200016:08:17

This is my first proper Open source library so I'm trying to find the 'proper' way

Joshua Suskalo16:08:07

Sure. Generally, dynamic vars would be recommended here for this usecase. You wouldn't even necessarily need to change your code almost at all. Just add a binding to set the vars to nil to start with at the entrypoint, and change your load-gdx-globals to use set! instead of set-var-root

mx200016:08:37

I will try that out! Thank yoz

Joshua Suskalo16:08:11

Now because you're handing over the core game loop to GDX (which as far as I'm personally concerned makes this a framework, not a library, but that's splitting hairs), this may or may not work. If it doesn't, you may have to stick with the way you're doing it now and this interop would act as a reasonable justification for doing it the way you are.

mx200016:08:18

Although then I cannot access the vars from the repl if they are only bound over the application functions ....

mx200016:08:02

Yes I believe it's a framework

Joshua Suskalo16:08:03

But generally in the Clojure world we try to do things in an order, and stick to the first one in the order that works. The order generally goes like data->functions->macros->dynamic-vars->global-state->jank and this falls squarely in "jank"

2
Joshua Suskalo16:08:33

Different developers will have different preferences on the order of things, and once you gain an intuition for things you'll generally jump immediately to the spot in the hierarchy you know will be needed for your application (and sometimes you'll be wrong and it could be done earlier in the hierarchy, and sometimes you won't care because you want something simpler or less work to maintain).

Joshua Suskalo16:08:49

But this is the general pattern for most of the clojure library world

mx200016:08:34

Thank you for your opinion! It's great that you take the time to answer here.

Per Nissilä09:08:52

Out of curiosity, what operating system are you using for developing in Clojure? I have a hard time deciding between linux or MacOs

p-himik09:08:59

Linux, and not just for Clojure or even development - it's my everyday system.

👍 4
2
djanus09:08:29

I’m using both. macOS is my daily driver for work these days, but I have a separate machine that I use for programming-for-fun, and that one runs Linux

👍 2
2
oly09:08:28

linux (solus) for me work and personally mainly because I just need it to work with up to date software

vemv10:08:59

if you happen to be an Emacs junkie, I find Emacs has nice extra affordances on macOS. Maybe there are a few paid developer tools like Git Tower, or postgresql UIs that might be not quite matched on Linux. Finally, you can consider core count. On Linux it's cheaper to get more cores. Clojure programs and tooling routinely benefit from those.

practicalli-johnny10:08:07

Linux since 1995, initially Debian for a few years then the Ubuntu distribution. Now I use https://regolith-desktop.com/ which provides the i3 tiling window manager on top of Ubuntu. I use Lenovo Thinkpad laptops as they have excellent Linux support For development on the go I also use F-Droid Termux on android devices (smartphone, tablet) with a full Clojure development environment installed https://practical.li/neovim/termux/ I use a Mac if required to do so for work, although the bespoke keyboard of a Mac takes some adjustment, so is initially less effective. I always use an external keyboard and trackball with a Mac as I find the ergonomics don't fit me (rsi and slower typing speed, etc.). I find the Mac window manager limiting and dated, although the command pallet is nice.

phill15:08:00

It can be a tough decision, Linux or MacOS, but I would say, if you can afford Linux, go for it.

lread16:08:07

I switched from macOS to Linux when Apple stopped delivering OS updates to my aging but still capable iMac. So that's a plus for Linux, you can stay OS-current even on older hardware. I use VirtualBox to do any testing I need for Windows and, somewhat ironically, macOS.

adham10:08:23

Hey all, I'm writing server-side Clojure and using jetty to serve the requests. Everything works fine when running GET requests on localhost:3000/api but now I want to test hitting the server from another machine on the same local network, I am trying curl 192.168.1.101:3000/api but it times out. I have tested with the following jetty setups:

(def server
  (run-jetty #'app {:host "192.168.1.101"
                    :port 3000
                    :join? false}))
and
(def server
  (run-jetty #'app {:port 3000
                    :join? false}))
This is app
(def app
  (ring/ring-handler
   (ring/router
    ["/"
     ["api" {:get serve-data}]]
    {:data {:muuntaja m/instance
            :middleware [[wrap-cors
                          :access-control-allow-origin ["*"]
                          :access-control-allow-methods [:get :post :put :delete]]
                         format-negotiate-middleware
                         format-response-middleware
                         format-request-middleware]}})
   (ring/routes
    (ring/redirect-trailing-slash-handler)
    (ring/create-default-handler
     {:not-found (constantly {:status 404
                              :body "Route not found."})}))))

thheller10:08:18

might be your firewall blocking the request?

adham10:08:36

Can't believe that didn't come to mind, I'll test it now.

adham10:08:47

It was that. Thanks!

👍 2
pablore22:08:55

Hi, I have a question regarding consumption of a lazy-seq. Say I have a seq with lots of values and I want to consume them in chunks of N at a time for side-effects (for example sending batches to a server). What would be the best way to do that? Using a while form?

dpsutton22:08:39

user=> (reduce (fn [acc chunk] (conj acc (count chunk))) [] (partition-all 3 (range 15)))
[3 3 3 3 3]

dpsutton22:08:54

partition your lazy-seq. reduce/loop/run! over the partitions

pablore22:08:58

Along the same line, I tried making a macro to make it less tedious to do this kind of pattern, but I’m getting a weird error. This is the macro:

(defmacro do-take
  "Consumes a seq with side effects, with chunks of size `n` at a time."
  [n binding & body]
  (let [[var s] binding
        partitioned-seq (partition-all n s)]
   `(loop [~var (first ~partitioned-seq)]
      (when-not (empty? ~var)
        (do ~@body
            (recur (rest ~partitioned-seq)))))))
And I’m testing it with (macroexpand-1 (do-take 3 [s (range 100)] (println s))) But am getting the following error: class clojure.lang.LongRange cannot be cast to class clojure.lang.IFn (clojure.lang.LongRange and clojure.lang.IFn are in unnamed module of loader 'app') . Is there a way to debug this?

dpsutton22:08:39

you’re mixing code at compilation versus code you emit. partitioned-seq is operating on values at expansion time. You need to emit code to do what you awnt. You cannot work on the values during expansion

pablore22:08:19

So putting everything inside backquotes should do the trick right?

dpsutton22:08:29

i think there’s no need for a macro here. But being clear about emitting code vs code used to emit code is foundational to macro writing

Joshua Suskalo15:08:44

be aware that partitioning doesn't fix issues with chunking. In order to consume elements to produce a partition element, an entire chunk of the input sequence will be realized.

Joshua Suskalo15:08:15

You have to construct your initial lazy sequence in a way that will not chunk and then process it in a way that will not introduce chunking.

Joshua Suskalo15:08:30

This is why lazy sequences are not recommended for side effects.

Joshua Suskalo15:08:16

That said, it is not particularly difficult to construct a transducer context which produces a non-chunked lazy sequence which you can apply transducers to instead of sequence ops, which gives you similar flexibility without introducing chunking.

pablore19:08:21

So, if sequences are bad for doing side effects, is there other structure I could use to stream the file and do side effects while I consume it?

Joshua Suskalo20:08:54

Explicit imperative code makes when and how much IO occurs extremely apparent. If the sequence api is important to your desired workflow you could consider constructing a transducing context that meets your specific needs while still providing flexibility.

Joshua Suskalo20:08:58

If the laziness itself is important but you don't need to be concerned with consuming too much of something, lazy seqs can still be a good fit.

Joshua Suskalo20:08:52

It's just that precise IO and chunking of lazy sequences act counter to each other, and clojure's sequence library favors chunking.

👍 2