clojure

Emanuel Rylke 2025-11-06T09:50:08.355919Z

Looking at the implementation of postwalk-replace in clojure.walk made me wonder why the author didn't use #(smap % %) or #(get smap % %) instead of (fn [x] (if (contains? smap x) (smap x) x))

p-himik 2025-11-06T09:58:16.550019Z

Probably no good reason. It's easy to write such code when you're in a kind of flow.

šŸ™Œ 1
dpsutton 2025-11-06T13:21:00.071449Z

Also nil and false values can get tricky with this style. Wouldn’t affect this version but sometimes easier to code it this way so it’s not a factor

Jim Newton 2025-11-06T10:39:08.617959Z

I’m in the middle of refactoring some code into subdirectories. and after the refactoring I’m getting a java NullPointerException which I don’t understand, and I get no stacktrace to help, Unfortunately the code involves the spec API which I don’t really understand. 1. is it normal that we get no stacktrace when we get a NullPointerException? 2. what’s some advise of figuring out what’s happening? I’ve put a bunch of print statements into my code and i’ve almost poinpointed the place where it is happening. And I suspect it is happening in a local function which clojure has given a cryptic name to. although i’m not 100% sure. #function[genus.genus-spec/spec-to-rte/fn--18016] Because, indeed my function spec-to-rte declares several local functions using letfn but I don’t know which one is fn-18016 .. why doesnt the name mangling use the name given in letfn to create the name of the function? Or perhaps it does and my problem is elsewhere?

Jim Newton 2025-11-06T10:45:07.318949Z

finally found my problem, just by looking deeper and deeper. maybe there’s no easy way. I just had to do the work.

p-himik 2025-11-06T10:46:09.082729Z

> is it normal that we get no stacktrace when we get a NullPointerException? Kinda. Most likely, that's just due to how the JVM is configured to work by default. You can change it by providing a -XX:-OmitStackTraceInFastThrow flag to java. So prepend -J to it if you use clj. > what’s some advise of figuring out what’s happening? Refactoring piece by piece, where each piece is as small as reasonably possible, with checks in between. > Because, indeed my function spec-to-rte declares several local functions using letfn but I don’t know which one is fn-18016 .. Functions created with letfn have a name, and that name will be in their string representation. fn--... tells us that it's not one of those functions at the top level of letfn but rather some anonymous function - look for (fn [...] ...) or #(...).

Jim Newton 2025-11-06T10:47:15.231849Z

> Functions created with letfn have a name, and that name will be in their string representation.

Jim Newton 2025-11-06T10:47:20.456219Z

that’s good news.

Jim Newton 2025-11-06T10:48:52.886629Z

ahh that makes sense. I found where I’m creating some other anonymous functions … perhaps I should try to name these in the macro expansion.

(defmacro casep
  "Like case, but uses the given test function rather than = to test
  whether the given object corresponds to any of the given choices."
  [test obj & pairs]
  (loop [pairs pairs
         default (fn [] nil)
         acc ()]
    (cond (empty? pairs)
          `(-casep-helper ~test ~obj ~default ~@(reverse acc))

          (empty? (rest pairs))
          (recur ()
                 `(fn [] ~(first pairs))
                 acc)

          :else
          (let [key (first pairs)
                value (second pairs)]
            (recur (rest (rest pairs))
                   default
                   (cons `['~key (fn [] ~value)] acc)
                 )))))

p-himik 2025-11-06T10:55:20.504739Z

Yeah, and fortunately the (fn some-name [...] ...) form is possible. :)

ā˜ļø 1
Jim Newton 2025-11-06T11:01:05.211989Z

found my issue. I’ve changed several of my name space names (because of directory refactoring). genus-spec has become genus.genus-spec . Somewhere I’ve hard coded the name space name in a call to (find-ns 'genus-spec) which of course now returns nil Lo-and-behold, when i pass nil as first argument of ns-resolve we get this NullPointerException.

Jim Newton 2025-11-06T11:10:48.891599Z

BTW, i’m always a bit confused about clojure macros. Do I need to quote the (fn [] nil) which is the initial value of default in the loop? because it will be interpolated into the back-tick later ???

Jim Newton 2025-11-06T11:11:17.414099Z

otherwise i’m imbedding a live function object into a macro expansion.

p-himik 2025-11-06T11:13:04.159559Z

That's correct, it needs to be syntax-quoted. Same how you already do it in (recur () (fn [] ~(first pairs)) acc)`.

p-himik 2025-11-06T11:16:22.471029Z

Macros must produce a data structure that's a valid Clojure form. Quotes and syntax quotes are ways to create suitable data structures. But you could make do without them at the (...) level at all, by constructing lists manually: (list fn [] nil)`. Going further, even fn` can be replaced with (symbol "clojure.core" "fn"). Not that anyone should want to write or read stuff like that when quotes and syntax quotes exist, but it might help with grokking these things.

p-himik 2025-11-06T11:17:38.617799Z

> Not that anyone should want to write or read stuff like that when quotes and syntax quotes exist Just to expand on this a bit - I meant specifically the example code in my message above, not in general. Sometimes you do want to use list and/or symbol functions explicitly, especially in more complex macros.

Jim Newton 2025-11-06T11:59:24.700669Z

I also find myself using template instead of back-tick because it gives more flexibility,

Jim Newton 2025-11-06T12:08:05.264469Z

> Yeah, and fortunately the (fn some-name [...] ...) form is possible. :) This reminds me of my gripe that clojure lacks homoiconicity. If I want to add the name in the fn form, the following fails.

(defmacro casep
  "Like case, but uses the given test function rather than = to test
  whether the given object corresponds to any of the given choices."
  [test obj & pairs]
  (loop [pairs pairs
         default `(fn casep-nil-default [] nil) ;; TODO name this fn
         acc ()]
    (cond (empty? pairs)
          `(-casep-helper ~test ~obj ~default ~@(reverse acc))

          (empty? (rest pairs))
          (recur ()
                 `(fn casep-specified-default [] ~(first pairs)) ;; TODO name this fn
                 acc)

          :else
          (let [key (first pairs)
                value (second pairs)]
            (recur (rest (rest pairs))
                   default
                   (cons `['~key (fn [] ~value)] acc) ;; TODO name this fn
                 )))))
because backquote thinks casep-specified-default and casep-nil-default are vars. Instead I have to use ~'casep-specified-default etc.
(defmacro casep
  "Like case, but uses the given test function rather than = to test
  whether the given object corresponds to any of the given choices."
  [test obj & pairs]
  (loop [pairs pairs
         default `(fn ~'casep-nil-default [] nil) ;; TODO name this fn
         acc ()]
    (cond (empty? pairs)
          `(-casep-helper ~test ~obj ~default ~@(reverse acc))

          (empty? (rest pairs))
          (recur ()
                 `(fn ~'casep-specified-default [] ~(first pairs)) ;; TODO name this fn
                 acc)

          :else
          (let [key (first pairs)
                value (second pairs)]
            (recur (rest (rest pairs))
                   default
                   (cons `['~key (fn [] ~value)] acc) ;; TODO name this fn
                 )))))

p-himik 2025-11-06T12:33:01.462659Z

How is it related to homoiconicity though? > A language is homoiconic if a program written in it can be manipulated as data using the language. That's still perfectly the case here. It's just that the behavior of syntax quotes requires you to circumvent it in some specific cases. > because backquote thinks casep-specified-default and casep-nil-default are vars. No, syntax quotes have no idea and no care about vars. And macros don't care about vars as well. Vars come into picture much later. The only thing that's important here is symbols. A syntax quote adds namespaces to symbols, that's it, as documented here: https://clojure.org/reference/reader#syntax-quote For the syntax quote to behave differently, it would have to either be omniscient or become significantly less general.

2025-11-06T20:21:08.139199Z

Clojure golf question: I want to update-in a field if and only if: a. The path does not already have some value in it b. Whatever value I generate does have some value So far I have:

(defn get-program [etype field]
  :the-program ;; can be null
  )

(let [m  {} ;; some map 
      etype "users" ;; some k 
      field "email" ;; some v
      ]
  (update-in m [:etype->programs  etype :fields]
             (fn [field-programs]
               (if (contains? field-programs field)
                 field-programs
                 (if-some [p (get-program etype field)]
                   (assoc field-programs field p) 
                   field-programs
                   )))))
Would you write this differently?

Maravedis 2025-11-06T20:27:34.448999Z

I'm not sure this code is doing what you want it to do, so before answering, I have a question : are you sure about this (contains? field-programs etype) ? Isn't it to be field? To me it reads as if you want to assoc in field if and only if that field is not yet set in field-program. Am I wrong?

2025-11-06T20:29:24.602959Z

Sorry about that, updated! I want to say something along the lines of: assoc p in field if and only if: • field is not yet set • and p is not nil

dpsutton 2025-11-06T20:30:32.080789Z

use fnil as the function to update?

teodorlu 2025-11-06T20:31:13.313309Z

fnil would still assoc if p is nil

Maravedis 2025-11-06T20:34:07.966679Z

Also wouldn't work, you'd have to use or. Like if associating nil wasn't a problem, you could just:

(update-in m [:etype->programs etype :fields field] #(or % (get-program etype field)))

teodorlu 2025-11-06T20:34:14.160929Z

It sounds like you want default values. Have you considered merge?

borkdude 2025-11-06T20:37:27.588579Z

I would perhaps just do the naive thing of using get-in to see if the value is there and if not, execute the function and only then use assoc-in to set the new value, to prevent creating too much garbage by updating for nothing

Maravedis 2025-11-06T20:40:26.655769Z

Maravedis 2025-11-06T20:40:47.045619Z

Smthing like this? You do make one unecessary op if field exists.

borkdude 2025-11-06T20:41:39.518209Z

I would use a not-found sentinel with get-in to check for nil or false fields, unless that's not what stopa is expecting in those fields

2025-11-06T21:36:02.348999Z

That could work! Thanks team

shaunlebron 2025-11-07T01:35:46.902609Z

I like to collapse nested conditions when they share the same fallback values like this:

(let [m {} ;; some map 
      etype "users" ;; some k 
      field "email" ;; some v
      ]
  (update-in m [:etype->programs etype :fields]
    #(or (when-not (contains? % field)
           (when-some [p (get-program etype field)]
             (assoc % field p)))
         %)))

ā¤ļø 1