FYI, I started a discussion for an idea on how Babashka could fake the feature of allowing the implementation of well known Clojure/Java interfaces. This would allow to create custom types that work like maps, sets, etc e.g. something like https://github.com/Malabarba/lazy-map-clojure/. I am curious to hear your opinions https://github.com/babashka/babashka/discussions/1884
I've thought about this before and in fact IDeref and IAtom work like this. But with other interfaces like ILookup there's the problem that you have to patch core functions that use get and all other functions that use those core functions if they are not loaded from source but pre-compiled
Yeah I think I mentioned that the example, unless you mean something different. I don't know if the performance implications are significant or if there are other problems i didn't think about.
If it's possible to patch Clojure's ILookup protocol and somehow hack that, that would be an option. In CLJS this is possible and I've thought about doing this for SCI on JS
well you mention that you should patch get, but you should also patch all other functions that use get
Ah there you go, that was the thing i didn't think about yet. Hmm that might be more complicated then
unless you can patch clojure's ILookup somehow
Would it be possible to have the ILookup interface call the ILookup protocol? Or maybe that is too low level to work
I think here is an example of messing with clojure's ILookup: https://github.com/phronmophobic/scify
probably doesn't work for AOT-code, but reloading code should fix that
This is hacking from the outside I guess, but since you control babashka it would be technically possible to change the actual clojure source code if that would help
yeah indeed
Not saying it is worth it, but just as an idea
it's actually something that might be worth it
It is already amazing how many things I can already do just with Babashka, but for this particular case for a custom type I cannot find a hack that works for me
I don't know if @smith.adrianeβs hack is going to work for ILookup. When you apply this hack, get is probably just going to use the inlined protocol stuff instead of the hacked var
you mean the reitit thing right?
I think the reitit thing isn't going to perform well in SCI compared to just doing it the straightforward way like e.g. https://github.com/tonsky/clj-simple-router which works in bb. The more you lean on pre-compiled core functions, the faster it'll get. But I haven't benchmarked anything so I might be wrong
It's related, as I build upon Reitit, but this for a tool like Sinatra (in Ruby). It sort of works now with Babashka, but I would like to have something magic with request params and I can only do it nicely with a custom type i think
You can make custom maps like this here: https://blog.wsscode.com/guide-to-custom-map-types/
scroll down to CMT in Babashka
Ah cool that might be what i need. Thanks! I'll have a look at and let you know later
O wait, I overlooked something. ILookup is of course an interface.
not a protocol. So I can't patch it
yeah I guess I could fork clojure... :-s
Yea, I think scify only works for protocols and only when you are building the sci ctx yourself (ie. not babashka compatible).
yeah you mean: not from source. that's not expected of course. we were just looking for a hack to make ILookup properly work within SCI in combination with pre-compiled stuff
I guess I could still fork clojure and patch get
maybe just reloading clojure.core + patching get would get rid of the AOT inlining effect
no get has an inline thing. patching clojure.lang.RT/get would be more effective
this still wouldn't solve the problem for other Java interfaces though which are common in deftype / reify
Yeah sounds like it is quite tricky. I will think about what you said. I didn't think it through that much before
Btw, the proxy approach works. So that's already nice
I am curious what the usual approach is for deploying a babashka script that is part of a project that also has non CLI, web app functionality. I have a project that is essentially a pedestal app. There is a companion command line tool, also in the project, done via babashka script (which uses some project namespaces) at the root project level alongside a bb.edn. This works well locally as a project dir, I can run the script as well as load/launch web app stuff from a repl. But this all ultimately needs to run on the server. To deploy as a web app there I make an uberjar of the project via tools.build and put this on the server and it gets launched and run as a service. Is there some way to run the babshka script from within that project uberjar? Or do I need to do something else to make it available? Maybe I should https://book.babashka.org/#_uberscript separately? Is that what people usually do? Or I could just git clone the project dir onto the server and setup the bb cli path as I do locally. Thanks for any general guidance π
if the bb sources are in the jar, you should be able to use them via bb βclasspath project.jar ,,,
I have you have tasks, you can put them in resources/META-INF/bb.edn (so on the classpath in META-INF ), then you can also use them with a jar (without having them on disk)
Thank you both for the info!! Very helpful π
My bb script (and its dependencies) are in the jar but how would I give the path to it? I tried bb -cp project.jar toolname, where toolname is at the root of the project dir, but I get an error "File does not exist."
Or do I need to have a copy of the script outside the jar? (Pretty easy, it just invokes one of the fns in the jar)
(Going to send this to the main channel for visibilty also but if this is considered rude please tell me π )
Also when I try to invoke the script alongside the jar, I get a different error, which is odd because it's an sci error I never get when invoking the script locally without the jar (with project dir). Edited for brevity:
bb -cp target/myapp-1.0.336-standalone.jar toolname
----- Error --------------------------------------------------------------------
Type: clojure.lang.ExceptionInfo
Message: Invalid assignment target
Data: {:type :sci/error, :line 1962, :column 3, :file "clojure/spec/alpha.clj", :phase "analysis"}
Location: clojure/spec/alpha.clj:1962:3
Phase: analysis
----- Context ------------------------------------------------------------------
1958:
1959: Initially set to boolean value of clojure.spec.check-asserts
1960: system property. Defaults to false."
1961: [flag]
1962: (set! (. clojure.lang.RT checkSpecAsserts) flag))
^--- Invalid assignment target
[... cut ...]
----- Stack trace --------------------------------------------------------------
clojure.spec.alpha/fn - clojure/spec/alpha.clj:1962:3
[... cut ...]
clojure/spec/alpha.clj:1955:1
my.app.cli - my/app/cli.cljc:3:3
my.app.toolname - /path/to/toolname:4:3
oh hmm, clojure.spec is in your jar, which is a problem for bb since then it's going to try to load it from source
which it can't
ah ok - maybe i will make a different jar/project for the cli tool
yeah that would probably be best
you can do so with bb uberjar myjar.jar -m the-main-fn
ok cool
thank you!
I tried bb -cp project.jar toolname, where toolname is at the root of the project dir, but I get an error "File does not exist."
Are you 100% sure a file exists on the filesystem in that directory named toolname?The file definitely exists at the root of the project dir /that i built the jar from/, but not in the dir where i was typing the bb command, if that makes sense.
bb file is never going to load something from a jar file, always file system
ah ok
my bad
if you want to load a namespace from the classpath that can be done using:
bb -e "(require 'my-namespace)"or if you have a function defined like:
(ns myns)
(defn foo [{:keys [x y z]}]
(do-something))
then you can run it with bb -x myns/foo --x 1 --y 2 --z 3or -main:
(ns myns)
(defn -main [& args]
(do-something))
bb -m mynsOh those are good ideas to avoid moving the script. Right now the script is just an ns with a script guard -- (when (= *file* (System/getProperty "babashka.file"))) -- but I could define an fn in there with the same contents as what is inside the guard
yep
that's a common pattern. define a function and call it in the guard