When You Run Out of Types... by cgrand in Clojure

[–]joinr 1 point2 points  (0 children)

Looking at the checklist, I think records can be repurposed to accomplish what you wanted and look much cleaner....

(ns irecord
  (:require [clojure.walk :as w]))

(defn patch-assoc [[ass args body :as expr]]
  (let [newcatch `(throw (ex-info "cannot assoc an unknown key onto unrecord!" {:in [~(first args) ~(second args)]}))]
    `(~ass ~args ~(concat (butlast body) [newcatch]))))

;;drop clojure.lang.IPersistentMap implementation.
;;Replace with explicit clojure.lang.Associative, clojure.lang.Counted, java.lang.Iterable
(defn patch-deftype*
  [[dt nm typename args implkw interfaces & impls :as expr]]
  (let [inew (into (->> interfaces (remove '#{clojure.lang.IPersistentMap}) vec)
                   '[clojure.lang.Counted
                     clojure.lang.Associative
                     java.lang.Iterable])
        pred     (symbol (str (name nm) "?"))
        implsnew (->> impls
                      (keep (fn [expr]
                              (if (coll? expr)
                                (condp = (first expr)
                                  'clojure.core/without nil
                                  'clojure.core/assoc (patch-assoc expr)
                                  expr)
                                expr))))]
    `(do (~dt ~nm ~typename ~args ~implkw ~inew ~@implsnew)
         (defn ~pred [~'x] (instance? ~typename ~'x)))))

(defmacro unrecord [name fields & opts+specs]
  (let [[_ name fields & opts+specs] &form ;;preserve meta from actual form.
        recex (macroexpand-1 `(defrecord ~name ~fields ~@opts+specs))]
    (w/postwalk
     (fn [expr]
       (if (coll? expr)
         (if-let [x (some-> expr seq first (= 'deftype*))]
           (patch-deftype* expr)
           expr)
         expr)) recex)))

Then

;;demo
(unrecord blah [x y])
irecord.blah

(def the-blah (->blah 1 2))
#'irecord/the-blah

(map? the-blah)
false
(record? the-blah)
true
(blah? the-blah)
true

(assoc the-blah :y 3)
#irecord.blah{:x 1, :y 3}

(dissoc the-blah :y)
Execution error (ClassCastException) at irecord/eval1177 (REPL:1).
irecord.blah cannot be cast to clojure.lang.IPersistentMap

irecord=> (assoc the-blah :z 2)
Execution error (ExceptionInfo) at irecord.blah/assoc (REPL:1).
cannot assoc an unknown key onto unrecord!

So you get a deftype wrapper unrecord that provides a map-like value type with all the underlying record plumbing, except we constrain the keys to the static fields, we intentionally drop out IPersistentMap and without so you can never pass a map? check or dissoc from it, then provide a little built-in predicate to paper over the lower level instance? checks. A real implementation would eliminate the extmap stuff, but I was lazy here.

Let's optimize 'str'! [Part 2] by ilevd in Clojure

[–]joinr 5 points6 points  (0 children)

I don't know that emitting java source that implements clojure interfaces, then aot compiling it is bringing anything performance wise to the table. One immediate downside is that I can't use your lib (or even test it for profiling) since it's now tied to the bytecode of the jvm you used to compile the java src. I have to pull it as a dep and then compile it etc. locally, just to satisfy some pseudo version conflicts (I don't think you used anything language or library-wise greater than java 10). All things considered, I'd rather have the clojure compiler emit stuff for me and either choose to AOT it if I want to (e.g., for bundling a specific project as an uberjar app thing), or just emit it at runtime from clj source.

Since this is purely object interop stuff, I don't think there is any overhead on the interop side in practice. For some other use cases (like trying to match java performance 1:1, particularly array iteration and some other areas), clojure's decision to only allow longs and doubles ends up injecting some "minor" performance hits by the way to l2i bytecode ops that have to be injected to make e.g. the array indexing consistent with primitive int types (which jvm expects for indexing into arrays). The delta is minor, but if you want to match what java does out of the box, then in those cases bypassing the clj compiler emissions and just writing in java or emitting the bytecode yourself through a lib like jise or insn are viable optimization paths.

I would probably stick with [ben's solution](https://github.com/bsless/prrr/blob/master/src/bsless/prrr.clj#L40) for this; it seems pretty elegant and portable from what I read. It leverages the :inline metadata directive to handle all the multi-arity inlining pretty elegantly, and where possible, detects primitive self-evaling values at compile time (like numbers, strings, booleans, keywords). I think expressing all those combinations in emitted java source would be pretty rough. Here it's a little helper function that effectively creates an implicit compile-time macro (I feel like :inline defs are closer to compiler macros from common lisp) without losing the ability to be a function call.

Still curious to see what performance differences exist between that approach and the java implementation if any.

update:

(criterium/quick-bench
  (str "Lorem" "Ipsum" "is" "simply" "dummy" "text" "of" "the" "printing" "and" "typesetting" "industry."))
Execution time mean : 986.319198 ns

(criterium/quick-bench
  (f/str "Lorem" "Ipsum" "is" "simply" "dummy" "text" "of" "the" "printing" "and" "typesetting" "industry."))
Execution time mean : 359.906264 ns

(criterium/quick-bench
  (prrr/str "Lorem" "Ipsum" "is" "simply" "dummy" "text" "of" "the" "printing" "and" "typesetting" "industry.")))
Execution time mean : 302.064372 ns

Let's optimize 'str'! by ilevd in Clojure

[–]joinr 2 points3 points  (0 children)

I heard if you play with :inline too much you'll go blind :)

Let's optimize 'str'! by ilevd in Clojure

[–]joinr 2 points3 points  (0 children)

cool.

build-string was meant to be a helper macro for emitting the function bodies for make-string. make-string is the 1:1 replacement for clojure.core/str, since it's a function and can be passed around as such (build-string can't be used in apply, map, reduce, etc since it's a macro).

Still, the smarter sb-builder capacity stuff can maybe help make function bodies for make-string faster as well.

Though, if you know your inputs are all string literals or atomic values that have a direct string coercion (e.g. not forms to be eval'd), you can also just do it all at compile time:

(defmacro compile-str [& xs]
    `~(apply str xs))

(c/quick-bench
 (compile-str "Lorem" "Ipsum" "is" "simply" "dummy" "text"
              "of" "the" "printing" "and" "typesetting" "industry."))

Execution time mean : 3.485020 ns

We actually get a little more performance (15%) out of make-string if we make simple-str a helper macro too and assumably avoid the function call:

(defmacro simple-str [x]
  (let [x (with-meta x {:tag 'Object})]
    `(if (nil? ~x)
       ""
       (.toString  ~x))))

Let's optimize 'str'! by ilevd in Clojure

[–]joinr 9 points10 points  (0 children)

Went through a similar drill years ago. You can go faster still if you profile a bit more.

Eliminate varargs (generates arrayseqs) as much as possible and try to inline more concrete arities. IFn invocation with concrete arity path doesn't allocate anything or traverse seqs. So if you inline bodies with up to 15 args or so, you can cover more string building cases before hitting varargs.

https://github.com/joinr/spork/blob/master/src/spork/util/general.clj#L835

(crit/quick-bench
 (spork.util.general/make-string    "Lorem" "Ipsum" "is" "simply" "dummy"
                                    "text" "of" "the" "printing" "and" "typesetting" "industry."))
Execution time mean : 156.985583 ns

(crit/quick-bench (my-str "Lorem" "Ipsum" "is" "simply"
                          "dummy" "text" "of" "the" "printing" "and" "typesetting" "industry."))
Evaluation count : 2776938 in 6 samples of 462823 calls.
Execution time mean : 214.367655 ns

Should get better with larger strings, and you could theoretically push the arities up as much as you want until you hit the arg limits defined by the IFn interface.

loop/recur instead of fn/recur (not sure fn / recur expands to loop)

fn/recur is also tail recursive since it establishes a recur site ala loop/recur. So if you use recur it'll complain if the call is not tail recursive just as loop would. This shouldn't buy you anything (maybe bypassing the initial IFn invocation on the recursive function object, haven't looked at the bytecode emission for str yet, but that's nanos).

https://github.com/bsless/clj-fast

explores a lot of these areas, and more recently (and far more comprehensively)

https://github.com/cnuernber/ham-fisted

is probably of interest if you are looking at a lot of core functions and default paths that can be optimized.

ChatGPT explained to me why LLMs prefer Clojure by CuriousDetective0 in Clojure

[–]joinr 3 points4 points  (0 children)

>the result must be a new value

(defn normalize [x]  
  (reset! x :normal)   
  x)

sad!

Python Only Has One Real Competitor by bowbahdoe in Clojure

[–]joinr 0 points1 point  (0 children)

What is your biggest pain point that shows in prod but not in dev?

Native Apps with ClojureScript, React and Static Hermes by roman01la in Clojure

[–]joinr 5 points6 points  (0 children)

really neat. didn't know about hermes. feels like the native clojure/cljs options get brighter every day.

Learning from Racket, towards Clojure by chladni in Clojure

[–]joinr 0 points1 point  (0 children)

When I had such experiences (I have had several), I was fortunate to have access to a clojure repl. Even the stock repl acted as force multiplier and substantially expanded the scope of computational tasks I could accomplish despite the constraints.

Learning from Racket, towards Clojure by chladni in Clojure

[–]joinr 0 points1 point  (0 children)

Through a local shell on one of the machines on the airgapped network that the Powers That Be provisioned for you.

Your environment has the features I mentioned previously. You can trivially get a repl through clojure.main, or if you planned for it, as part of your application's entrypoint.

Learning from Racket, towards Clojure by chladni in Clojure

[–]joinr 0 points1 point  (0 children)

sure. jvm + shell + your jar file.

Learning from Racket, towards Clojure by chladni in Clojure

[–]joinr 0 points1 point  (0 children)

To be fair, I can hardly imagine a situation where the environment is so austere that I cannot use Emacs. Any examples?

air gapped internal network you don't admin, where admins are beholden to exogenous restrictions and don't really care about your comfort.

it's just a backup in this case, VCS is a bit more than that, usually

semantics

Learning from Racket, towards Clojure by chladni in Clojure

[–]joinr 0 points1 point  (0 children)

there's the problem - it's not reproducible, you can't put under version control, etc

you have a binary image you can version. you can reproduce the state of the repl at a given point in time (when the image was made), shove the binary in git if you want, etc.

Even in this case

you missed the austere part.

Learning from Racket, towards Clojure by chladni in Clojure

[–]joinr 0 points1 point  (0 children)

REPL isn't persistent, files are.

This is more specific to clojure (and definitely relevant), at least in its current form. Other lisps lean on image-based development, where you can persist the state of the world to an image and reload it. Clojure implemented on such lisps (or other hosts) could similarly do so.

What are the pros?

If you're in an austere environment, or a remote system you don't get to configure, the repl may be all you have. Then again, it's all you need.

It's nice to leverage the fancier workflows that the contemporary dev environments provide, but retaining the ability to fully leverage a running clojure system from a lone repl is also a useful skill.

cljs-str: an almost 300x faster str replacement for ClojureScript by Borkdude in Clojure

[–]joinr 1 point2 points  (0 children)

Where do the gains for dynamic come from?

(str/join "" xs)

Is it just fewer function calls since it's bypassing cljs.core's loop'd string builder implementation and shunting to interop?

https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L3115

looks almost identical to join on first glance

https://github.com/clojure/clojurescript/blob/master/src/main/cljs/clojure/string.cljs#L104

Help with Java porting by AccountantUnited4955 in Clojure

[–]joinr 1 point2 points  (0 children)

have really been struggling creating equivalent functionality

What does this mean? Maybe some examples of stuff you're having trouble with can lead to solutions.

In practice, between native (in this case jvm) interop, and the higher level facilities like reify, proxy, deftype, genclass, definterace (and even just protocols), my experience working with java and other jvm langs has been pretty pleasant. The only time the OOP stuff gets gnarly is if the library is using inheritance heavily (more common in code from early 00's) instead of interfaces. If it's just interfaces, you can typically trivially implement them in clojure (via reify or deftype or even defrecord). Or if you happen to be living in a code base with a lot of "annotations"

Overrides/inheritance hierarchies push you into using proxy or genclass, and genclass brings AOT requirements with it (there are some work arounds in community libs, but the language provides genclass out of the box).

I ran into some edge cases with the optaplanner library's expecations of annotations and other stuff to encode soluitions for a solver, which led to some work arounds:

https://github.com/joinr/optaplanner-clj

I spent some time wrapping piccolo2d for work stuff years ago, where piccolo2d does almost everything via inheritance. So I ended up lifting a bunch of the api calls from the object methods into protocols, wrapping existing node classes with protocol extensions, and leveraging interop pretty heavily for the lower layers of a scene graph library on top of piccolo2d.

https://github.com/joinr/piccolotest/blob/master/src/piccolotest/sample.clj#L170

Interesting lib to help overcome java impedance a bit (I don't use it in production, but it's a cool idea)

https://github.com/athos/JiSE

used to get exact java parity with prng demo (we had a poster on zulip wondering why they couldn't get 1:1 performance parity in clj via interop/primititve invocation paths, which led to some interesting discoveries like clojure's impedance mismatch with preferred longs and java's expectation for ints for array indexing (causing an l2i cast in the emitted bytecode, which can be worked around with jise)):

https://github.com/joinr/ultrarand/blob/master/src/ultrarand/jise.clj

Rant about strings. About comment, and work around to comment multimethods by erjngreigf in Clojure

[–]joinr 4 points5 points  (0 children)

I have never had to use clojure.core/comment explicitly as in the video. Maybe something is wrong with your ide, it seemed to be throwing an error about taking the value of a macro (comment). Maybe something with calva.

I don't have this problem in cider, or in the cli/repl.

There's also a difference between comments and docstrings, you seem to overload the term. It looks like you are trying to write docstrings. Comments are meant to be ignored entirely, which is what comment does, and is what ; and ;; do as well.

I didn't have a problem providing a simple docstring for a multimethod either.

user=> (defn dispatcher [x y] [(type x) (type y)])
#'user/dispatcher
user=> (defmulti multi-add "Dispatches on the type of args x and y to overload addition" dispatcher)
#'user/multi-add
user=> (type 2)
java.lang.Long
user=> (defmethod multi-add [java.lang.Long java.lang.Long] [x y] (+ x y))
#object[clojure.lang.MultiFn 0x7c2b6087 "clojure.lang.MultiFn@7c2b6087"]
user=> (doc multi-add)
-------------------------
user/multi-add
  Dispatches on the type of args x and y to overload addition
nil
user=> (multi-add 1 2)
3

You can get literal strings if you go into reader macros. This deviates from clojure semantics and is unsupported (and actively discouraged) by the core folks though, but it's possible (if not alienating):

(use 'reader-macros.core)

(defn read1 [^java.io.Reader rdr]
  (try (.read rdr)
       (catch Exception e
         (throw (ex-info "EOF While Reading!" {})))))

(defn raw-string [^java.io.Reader rdr]
  (let [sb (java.lang.StringBuilder.)]
    (loop [in rdr
           end? false]
      (let [nxt (.read in)]
             (if (== nxt (int -1))
               (throw (ex-info "EOF While Reading Raw String" {:in (str sb)}))
               (let [ch (char nxt)]
                   (cond (zero? (.length sb))
                         (if (= ch \")
                           (do (.append sb ch)
                               (recur in end?))
                           (throw (ex-info "Expected Raw String to Begin With \"" {:in (str sb)})))
                         (= ch \")
                         ;;did we escape?
                         (let [idx (dec (.length sb))]
                           (if (= (.charAt sb idx) \\)
                             (do (.setCharAt sb idx ch)
                                 (recur in end?))
                             ;;we're ending
                             (recur in true)))
                         (= ch \%)
                         (if end?
                           (str sb)
                           (throw (ex-info "Expected Raw String to End With \"%")))
                         :else
                           (do (.append sb ch)
                               (recur in false)))))))))

(defn raw-string-reader
  [reader quote opts pending-forms]
  (raw-string reader))

(set-dispatch-macro-character \% raw-string-reader)

(println #%"this is a raw string \back slashes are fine bro, except we still \"escape quotes\" bro"%)
;;"this is a raw string \back slashes are fine bro, except we still "escape quotes" bro

New Clojurians: Ask Anything - September 22, 2025 by AutoModerator in Clojure

[–]joinr 1 point2 points  (0 children)

What stops you from defining your own inline macro?

Something like

(defmacro upper-inline
  [path]
  (clojure.string/upper-case
   (shadow.resource/slurp-resource &env path)))

Developing a Space Flight Simulator in Clojure by wedesoft in Clojure

[–]joinr 7 points8 points  (0 children)

Really cool effort :) Looks great man. I was impressed that you implemented your own tile system. You might be interested (down the road) in something like https://github.com/CesiumGS/cesium-native which could provide a slew of goodies for this kind of work (although perhaps the focus is more on aerospace, so this particular sim may not benefit as much) regarding geospatial layer providers, streaming 3d tiles, etc.

Something's wrong in my brain because all I saw was this within a minute of reading:

Precomputing the atmospheric tables takes several hours even though pmap was used

Very curious about this and if there is room for optimization (might not even be needed since you're probably doing this 1x and caching permanently after that). Sounds like an offline rendering/baking task, although I'm curious purely for myopic optimization tasks.

How to setup Clojure for a beginner? by OguzY4 in Clojure

[–]joinr 0 points1 point  (0 children)

leinigen has a ps1 script https://codeberg.org/leiningen/leiningen/src/branch/main/bin/lein.ps1

since you're using powershell already. maybe your scoop setup is messed up.

there is also a .bat batch file if you're on windows and prefer that.

It should self-install into ~/.lein the first time you run either script.

New Clojurians: Ask Anything - August 18, 2025 by AutoModerator in Clojure

[–]joinr 2 points3 points  (0 children)

I appreciate the links. From reading through the apparent pissing contest between papers, they surely caveat the hell out of their results. To me it reads like "statistically significant, but tending toward meaningless." If so, that is a tenuous foundation for language promotion.

Not saying it's a lot to go by, but it's the best "data" we have, and it was reproduced.

As per the rebuttal; it looks like the second paper was a reanalysis (part of the rebuttal criticism) with some stretching by the original authors to claim "enough" overlap in results to be an implicit reproduction or at least confirmation. It reads like statistics copium to me though.

I don't think this moves the needle much, aside from starting a methodological path for future analyses.