This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-08-07
Channels
- # aleph (7)
- # announcements (1)
- # babashka (31)
- # beginners (18)
- # calva (23)
- # cljdoc (10)
- # clojure (74)
- # clojure-europe (42)
- # clojure-norway (10)
- # clojure-uk (1)
- # clojurescript (19)
- # core-async (2)
- # cursive (188)
- # data-science (11)
- # datahike (1)
- # datascript (4)
- # events (2)
- # figwheel-main (23)
- # fulcro (5)
- # gratitude (2)
- # honeysql (3)
- # hyperfiddle (120)
- # jobs (3)
- # lsp (3)
- # meander (6)
- # missionary (8)
- # nrepl (1)
- # off-topic (5)
- # rdf (11)
- # releases (4)
- # remote-jobs (1)
- # sci (5)
- # tools-build (3)
- # tools-deps (14)
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.
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))
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?@U0ALH6R89 a good question for http://ask.clojure.org
.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
Most likely because the public API encourages you to not lose the previous value of the var.
https://ask.clojure.org/index.php/13135/common-operations-to-set-var-root-why-not-in-clojure-core
I have used that pattern to set Clojure's own global vars. But as for my own programming, I avoid globals altogether.
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)
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)
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.
https://github.com/damn/gdl/blob/6ae193e95d591042aa29170832cd26148f91e9d8/src/gdl/assets.clj#L28
https://github.com/damn/gdl/blob/6ae193e95d591042aa29170832cd26148f91e9d8/src/gdl/game.clj#L52
The point here is that anything that changes over the course of a program should not be represented by the root binding of vars.
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!
Rebinding vars during dev time is obviously ok, which it seems that this is probably the primary usecase you have for it.
But in most of those cases a set-var-root! is unnecessary as you can simply change the definition and re-evaluate it.
Yes these variables live with the application window and only get reset during development reloading
I wouldn't really recommend having the application window be initialized as a def either, to be honest.
Loading the application code should not start the application.
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...
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.
That's a reasonable usecase.
Ah, I understand a bit better now.
looking in at the code.
I don't recommend using vars this way.
I recommend using dynamic vars.
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".
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 ?
Yes, to the library user there would not be significant difference. This is a question of idioms, not API.
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
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.
Although then I cannot access the vars from the repl if they are only bound over the application functions ....
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"
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).
But this is the general pattern for most of the clojure library world
Out of curiosity, what operating system are you using for developing in Clojure? I have a hard time deciding between linux or MacOs
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
linux (solus) for me work and personally mainly because I just need it to work with up to date software
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.
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.
It can be a tough decision, Linux or MacOS, but I would say, if you can afford Linux, go for it.
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.
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."})}))))
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?
user=> (reduce (fn [acc chunk] (conj acc (count chunk))) [] (partition-all 3 (range 15)))
[3 3 3 3 3]
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?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
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
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.
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.
This is why lazy sequences are not recommended for side effects.
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.
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?
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.
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.
It's just that precise IO and chunking of lazy sequences act counter to each other, and clojure's sequence library favors chunking.