I was attempting to use org.clojure/core.cache, but got a runtime error for java.lang.ref.SoftReference ; is this something (SoftReference) that can be added to Babashka?
@seancorfield the issue is that protocols in SCI aren't invoked via interop but via their proper protocol function names.
user=> (defprotocol Foo (foo [_]))
Foo
user=> (defrecord Dude [] Foo (foo [_] 1))
user.Dude
user=> (.foo (->Dude))
java.lang.NoSuchFieldException: foo [at <repl>:1:1]I could perhaps fix that, but doesn't seem to gain that much since it's not idiomatic to write it like (.foo ..) anyway?
I let an LLM write a more idiomatic version of the tests and those all pass
https://github.com/babashka/babashka/blob/master/test-resources/lib_tests/clojure/core/cache_test.bb
Hmm, I do notice it skipped a bunch of .has etc tests. I'll try to do a more faithful translation of the existing tests. The wrapped tests run as is
ok, now I started from scratch and guided claude code how to fix the tests:
All 19 tests pass with 297 assertions, 0 failures, 0 errors. The only change needed to the test file was:
1. Remove the cache type imports (loaded from source .clj now, not needed)
2. :reload-all → :reload
3. .lookup → lookup, .has? → has?, .hit → hit (protocol functions instead of Java method calls)
And we fixed babashka itself by adding java.lang.Object proxy support in src/babashka/impl/proxy.clj.Yeah, I don't know why @fogus specifically tests for the interop version as well as the protocol version in core.cache. Feels like an implementation detail to me, but core.cache has been around for a very long time so maybe that dates back to wanting to test the mechanics of it?
no sweat, I think bb has enough coverage in the adapted tests
Most likely any odd tests were from long ago when I was testing something odd… what that may have been has been lost to time.
I think we can add it but looking at the source of core.cache, I think we'll hit another limitation with deftype + Java interfaces. There's also a workaround for this. Perhaps the maintainer of core.cache will be open to making it bb compatible, or we could just add core.cache to bb if there's enough demand for it. What is your use case?
Just normal caching of expensive to compute values. Since it's non-trivial, it's probably not a good fit. I'm using Babashka to fire up a relatively long-running web service and doing some expensive calculations such as diffing text - that feels like an edge case. I just built a simple cache around an Atom instead. core.cache is oriented towards very long lived high throughput services.
> Since it's non-trivial, it's probably not a good fit. Can you explain this sentence a bit more?
Since it is not trivial to extend Babashka to support core.cache, and an edge case for Babashka usage, I don't think you should do anything.
This is all related to my use of Datastar with Babashka. With my fairly large UI, I'm finally hitting points where I notice Babashka performance vs. Clojure performance and I'm looking for optimizations. Again, I'm pushing things well beyond the common definition of scripting.
I didn't know you were using bb with datastar, that's cool :) are you using their official clojure sdk with bb?
yes. I mostly develop in Clojure with Cursive, but the same code runs fine (but a bit slower) as Babashka. I'm using their http-kit based adapter.
great :)
$ clj -M:babashka/dev -cp src/main/clojure -e "(require '[clojure.core.cache :as cache]) (def C1 (cache/fifo-cache-factory {:a 1, :b 2})) (def C1' (if (cache/has? C1 :c)
(cache/hit C1 :c)
(cache/miss C1 :c 42))) C1'"
{:a 1, :b 2, :c 42}I was using the clojure.core.cache.wrapped namespace and that's where I hit the failure. I'm actually good with what I have; again, my process is long-lived by script standards, but short-lived by service standards, and there's a reasonable point where I can clear the cache.
I know. I just couldn't resist
I know that feeling.
wrapped would work too
Ah, I didn't see what you were doing. You just snuck this into dev didn't you?
I only have it working on my local system
Well, I'll loop back after the next release ...
it needs changes in core.cache which probably will never be accepted :)
I could write a .bb only file that people could include in their own projects as a replacement
$ clojure -M:babashka/dev -Sdeps '{:paths ["src/main/clojure" "src/test/clojure"] :deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"} org.clojure/test.check {:mvn/version "1.1.3"}}}' -m cognitect.test-runner -d src/test/clojure
Running tests in #{"src/test/clojure"}
Testing with Babashka 1.12.215-SNAPSHOT
Testing clojure.core.cache.wrapped-test
ttl test completed 2000000 calls in 3618 ms
Testing clojure.core.cache-test
Ran 21 tests containing 373 assertions.
0 failures, 0 errors.core.cache maintainer here... what changes would be needed to support bb?
This should now work with bb from development (`bash <(curl https://raw.githubusercontent.com/babashka/babashka/master/install) --dev-build --dir /tmp`): https://github.com/borkdude/core.cache/commit/33fcb3006bba2d67e73ddb97718831824ae6bdfe
@seancorfield I had to change the deftype stuff to proxy and create my own factory functions so you don't create cache types with Foo. but rather with ->Foo.
I put it all in different .bb files to not interfere with the existing impl
Ah, that is pretty extensive, and I would imagine would affect performance if it was used on the mainline Clojure version... ...and I guess the changes are too extensive to hide behind reader conditionals.
I don't think it would affect performance in Clojure that much. Foo. vs ->Foo. shouldn't matter that much. The proxy trick is only necessary to make the caches behave like maps (lookup, etc). If we dropped that restriction for bb and people would only use the protocol, the diff would be smaller. But I guess people do use caches as maps
Yeah, I'd say most of the usage out there is cache-as-map TBH.
I'm fine keeping this in a branch somewhere until a next person asks about this and not doing anything with it at the moment
I did detect a small bug in SCI when implementing this and I added the SoftReference class so it's a good experiment anyway
Looking at your .bb version side-by-side with the .clj version, I realized that FnCache is broken (it uses BasicCache internally instead of FnCache).
broken in clj or bb or both?
Both, since the original is broken.
k
I was curious if the Babashka version would run as Clojure, so I replaced the .clj files in core.cache with the .bb versions (extension changed) and tried to run the tests:
Unexpected error (ClassCastException) macroexpanding clojure.core/proxy at (clojure/core/cache.clj:125:1).
class clojure.lang.Var cannot be cast to class java.lang.Class (clojure.lang.Var is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')
interesting :)
I can take a look at this discrepancy tomorrow
You can't proxy a protocol maybe? Only class or interface.
I think you're right...
;; Define a protocol
(defprotocol Greeter
(greet [this]))
;; Try to use the protocol directly in proxy
(def x
(proxy [Runnable Greeter] []
(run []
(println "Running from proxy"))
nil
user=> user=> user=>
(greet []
(println "Hello from protocol inside proxy"))))
Greeter
user=> user=> user=> Unexpected error (ClassCastException) macroexpanding proxy at (REPL:2:3).ah, but this works!
(defprotocol Greeter
(greet [this]))
(def x
(proxy [Runnable user.Greeter] []
(run []
(println "Running from proxy"))
(greet [] (println "Hello from protocol inside proxy"))))@seancorfield fixed JVM compat: https://github.com/borkdude/core.cache/commit/e112d6d6db2283c70a52736e9f8d70caa7a1445b
Hmm, perhaps I can special-case implementing custom maps in deftype so it will be compatible with bb...
similar to the proxy case
Thanks. Will take a look tomorrow probably.
no need. I already got it working in bb without any change to core.cache now :)
(locally)
The bb dev build should now support core.cache from source, no changes in source.
bash <(curl ) --dev-build --dir /tmp
Try it with:
$ /tmp/bb -Sdeps '{:deps {org.clojure/core.cache {:mvn/version "1.1.234"}}}' -e "(require '[clojure.core.cache :as cache]) (def c (cache/ttl-cache-factory {:token \"abc123\"} :ttl 1000)) (println (cache/has? c :token)) (Thread/sleep 1000) (println (cache/has? c :token))"
;; true
;; falseLooks good. I assume this is expected if I try to run the tests with bb:
ERROR in (test-soft-cache-ilookup) (/home/sean/oss/core.cache/src/test/clojure/clojure/core/cache_test.clj:442)
that the SoftCache can .lookup