babashka 2026-08-27

Hi there! So... I was trying something on Babashka, and I wanted to "stream" a print. I tried to use (binding [*out* <some-proxied-writer>] but it doesn't work - it crashes with

ArityException Wrong number of args (2) passed to: babashka.impl.proxy/proxy-fn/fn--24060
	babashka.impl.proxy.proxy$java.io.Writer$ff19274a.write (:-1)
	clojure.core/fn--7386 (core_print.clj:124)
Is there any alternative that I can use (besides reimplementing print)?

What was the original example, does it work in Clojure?

If so, then it may be a bug that could be fixed

ah yes, append is missing in bb's proxy impl. You can use this as a workaround:

(defn tapping-writer
  "Returns a java.io.Writer that calls f with each chunk of text written to it."
  [f]
  (let [os (proxy [java.io.OutputStream] []
             (write
               ([b]
                (if (bytes? b)
                  (f (String. ^bytes b "UTF-8"))
                  (f (String. (byte-array [(byte b)]) "UTF-8"))))
               ([b off len]
                (f (String. ^bytes b ^int off ^int len "UTF-8"))))
             (flush [])
             (close []))]
    (java.io.PrintWriter. (java.io.OutputStreamWriter. os "UTF-8") true)))

I took a survey across my projects and I found that in the vast majority of the time my tasks were all the same, or ideally they would have been, but drift has occurred. Some times that drift is intentional: a project has a slightly different build sequence. Other times it is not intentional: the linter or test runner incantation was improved in one place but not propagated elsewhere. I appreciate that the tasks are malleable and their provenance still explicit. A task's implementation doesn't need to live directly in bb.edn but it is important that the bb.edn makes it obvious where to find it. The docstring is very important too bb tasks is always my entrypoint to answering "ok how do I drive this project again?" after being away for awhile. To be honest, I'm not sure this feature is really needed given :exec-fn and co (this may be a bit of stockholm syndrome as I've just lived with it for so long). For more "advanced" cases I just give up on tasks completely and use the [tasks cli features](https://clojurians.slack.com/archives/C8NUSGWG6/p1785163693046319) or a proper top level babashka script (http://github.com/Ramblurr/spdx-util) Furthermore, our LLM assistants make what was once a tedious or verbose typing exercise into something pretty simple "We updated our bb.edn tasks in Project X please propagate that to Project Y" Here's an example extracted from my projects:

clojure
{:deps  {example/my-tasks {:git/url ... :git/sha ...}}
 :tasks {:requires ([example.my-tasks :as t])
         ;; the most common type of usage
         bump      {:doc     "Bump the version component in all libs"
                    :exec-fn t/bump-version}
         ;; an example of doing the original task + some extra work
         clean     {:override-builtin true
                    :doc              "Clean build artifacts"
                    :task             (do
                                        (t/clean)
                                        (shell {:dir "extra-thing"} "rm -rf zig-out zig-cache"))}

         ;; or just plain fn calling
         docs {:doc  "Generate all documentation"
               :task (gen-docs/generate! {:external-api-index-paths ["../clave/doc/api-index.edn"]})}

         ;; included for completness, i use compound tasks like this alot
         qa
         {:doc     "Run full local qa: format, lint, and test"
          :depends [build lint test]}}}

Another huge +1 to this being included, I've been doing a lot of FFI/FFM in Clojure recently and I think it is a real super power. As much as we bootstrapped Clojure on top of the JVM/Java ecosystem of libraries, there are really powerful libraries that are only accessible via FFI/FFM and it would be inspiring to wield that in babashka

I'm curious if your API could be used in JVM Clojure also? I use coffi extensively, but I'd really value a consistent clojure ffi/ffm api that works across Clojure, GraalVM Native Image and Babashka.

💯 1

I'll add some motivation: I regularly dip in and out of dozens of repos. The context switching is unavoidable, but I try to keep the cost of reorienting myself low by giving every project a familiar shape (This applies to projects with dev teams I consult with too). and bb tasks is the standard front door for me. The projects don't all work the same way, but the common tasks (testing, linting, building, and releasing) should be named, doced, and invoked consistently.

Yeah, I'll probably wait with merging this since it's not utterly urgent. And bb.tasks does have something new on master which allows you to plug in a subcommand structures from a library which is kinda cool and perhaps also covers this.

:tasks {my-cmd {:cmd my-library/cmd}}
bb my-cmd <TAB> # prints subcommands 

Yes, the intention is to make babashka.ffi available so everything works smoothly on the JVM too

🔥 1
3