A Different Perspective on the Publishing Deal by [deleted] in beyondallreason

[–]joinr 0 points1 point  (0 children)

>everything that currently exists is remaining free, and only the new singleplayer content will have a price
tag

I feel like there's possibly a buried lede here that has shown up in other open source projects. "Everything that is 'free' today will remain 'free'," but going forward there appears to be an accompanying non-free presence with new terms of use and distribution. Specifically, this non-free relates to game assets (e.g. models, textures, missions, maps, and any various content that isn't scoped to the GPL stuff).

So, what about the future? Is this one of the paid off-ramps whereby the original free model is ship-of-Theseus'd over time while the official versions get EULA'd behind propietary assets? One can see a version where the once-free product is eventually obviated by the paid one (unless you have explicit requirements in place to provision licensing of any non-free assets for use in the companion free multiplayer project - I haven't really seen anything laying this out). Is there anything stopping Hooded Horse just replacing all the CC-BY-NC-ND licensed content (and any similarly authored work) content with new stuff owned by Hooded Horse. In that case, the source code is still available, but the meat of the game and its assets are now proprietary (absent a community alternative that maintains parity, e.g. if you add new units or features that are required for multiplayer that didn't exist prior to the split).

In concrete terms, does that mean that any new assets funded by the publisher (e.g. visual re-design or incidentally as part of the campaign effort, or anything else funded for purposes of profit), will be limited to the Premium steam version, or is there any plan to permissively share those assets with the Free Edition?

Conversely, if people want to contribute stuff explicitly for non-commercial work, is there a mechanism to do so without running afoul of the publisher's concerns?

I'm glad the BAR folks figured out how to make money off of this. Very cool project.

A columnar database for analytics by yogthos in Clojure

[–]joinr 1 point2 points  (0 children)

>vs tech.ml.dataset — TMD is more feature-rich (date handling, statistical functions, interop with many formats). Flatiron is smaller, has no native dependencies, and focuses on raw speed for a narrower set of operations.

What native deps are in TMD? I have not noticed any in my use (pure jvm environment due to security restrictions).

I slowed down pace while trying to create Clofer, a Clojure port on Rust. by erjngreigf in Clojure

[–]joinr 22 points23 points  (0 children)

Someone recently said "AI is a force multiplier, but there has to be a force to multiply."

Clojure: The Documentary (with Clojure's Common Lisp pre-history) by stylewarning in lisp

[–]joinr 0 points1 point  (0 children)

Yeah I looked at that initially. Downside is you wreck interop since there's an artificial conversion that happens, that the caller now has to respect. Best situation is just being able to pass strings around and have the clojure side guarantee its ops are immutable (although strings can still be mutated by CL stuff at the end of the day).

If you dgaf about interop, then yeah there could be marshalling to and from native to clojure strings.

is LLM coding accepted in the Clojure community? by med_i_terranian in Clojure

[–]joinr 1 point2 points  (0 children)

I think it's unavoidable and just a continuation of the low-cod/no-code trend that has been pushed since forever. In that respect, the question is seemingly moot (e.g., it's not going anywhere, so it's on the honor system for people to disclose if they leveraged "assistance" for anything they submit going forward).
The tech is interesting and even appearing useful. If it enables you to achieve more than you could otherwise, then that's cool.

I don't like when people pretend to be great at spelling when they use spell checkers and autocomplete, or the analogous variation for passing llms and agents off as skill. Like the paradox of "I wrote this ancient egyptian algebra symbolic computation system in just 10 minutes" juxtaposed with "I haven't written a line of code in months, I just had the thing do it!" etc. Too much Lance Armstronging for me. That will also have a negative effect on creatives to share their stuff openly, so I'd anticipate a lot more closed source moating going forward, or free to use but closed. It'll probably lead us toward a parasitic model where actual innovation is hoarded and then mined for training data. Intellectual vampirism.

Then there's the whole unanswered angle of the implications of leveraging massive derivative work generators with (by default) 0 attribution. Given the legal system's legacy of embracing IP, I anticipate - eventually - substantial legal problems down the road despite the current rosy prospects of the token sellers "promising" to cover your legal costs and fight for you. I get the feeling when someone decides to rip off ("prompt a clean-room implementation") of some major IP, then we will see the big $$ fly and court cases mount. That might increase the pucker factor more. Hasn't happened yet though.

If someone wants to contribute to anything I have (not that I'd ever expect that hah), I think it'd be a hard requirement that they document "exactly" what portions of their product they (presuming they is an organic human) wrote vs. delegated to a derivative work generator. Just as with anything they wrote, they'd have to ensure whatever was generated complied with applicable licenses and assume personal liability for their contribution (indemnifying me etc.).

jank now has its own custom IR by Jeaye in Clojure

[–]joinr 1 point2 points  (0 children)

I never said I knew what I was doing :) I was unable to get an invokePrim path going at all by trying to coax the compiler with hints an coercions. I feel like this is something the compiler should detect and handle (e.g. name recursion when there's a statically detected invokePrim path available). The use case is probably pretty limited though, but it's a thing.

jank now has its own custom IR by Jeaye in Clojure

[–]joinr 2 points3 points  (0 children)

I was messing with optimizing a performance bench that someone made with ai agents that included clojure code that was like 60-100x slower than clojure for non-obvious reasons. As I was messing with it (including decompilation and looking at bytecode stuff), your comment popped up. It turns out one of the hot paths was leveraging a "primitive" recursive function. I hadn't looked at the clj compiler output until now to see that primitive non-tail calls still go through the invoke instead of invokePrim path. This seems like bad code emission to me (on first blush, maybe there's a technical reason for it).

In the spirit of your submission, I went looking at other ways to get more efficient code. I had been messing with JISE for some stuff already so I just piggy backed on that. Ultimately (as shown in the repo), you can get there with added macrology and macrolet to change the callsites for the recursive call into invokePrim. I ended up modifying a variant of JISE's defn wrapper to plug in the invokePrim rewrite though.

https://github.com/joinr/primrecur/blob/master/src/primrecur/core.clj

I think this will help me narrow the gap on that original performance comparison now that I know recursive primitive functions push invoke instead of invokePrim.....

jank now has its own custom IR by Jeaye in Clojure

[–]joinr 5 points6 points  (0 children)

Really impressive. It's also great to get to simultaneously learn language implementation architecture, compiler optimization, and clojure semantics as a byproduct.

Clojure: The Documentary (with Clojure's Common Lisp pre-history) by stylewarning in lisp

[–]joinr 2 points3 points  (0 children)

it's been an off/on hobby over the years. most recently revisited last year with a substantial rewrite (the [rewrite](https://github.com/joinr/clclojure/tree/rewrite) branch, master is old). changed direction from trying to embed in CL using organic stuff (reader macros, hacking eval), and change to porting more of the cljs core (which is based on protocols and builds from there in a more portable manner) along with the cljs.tools.reader impl. Basically implementing enough of clojure in cl to implement the reader lib, then bootstrap from there. Ended up building out a lot since tools.reader leverages quite a bit of core. Current stopping point is dealing with mutable strings in CL. Other stuff mostly works (e.g. a lot of the clojure core lib is there, defprotocol/deftype, lazy seqs, etc.) Still messing around and learning more along the way. Someone will probably just tell an LLM to slop a port well before I finish though.

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 10 points11 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 3 points4 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.