sci

Ludger Solbach 2026-06-15T13:33:56.052139Z

In overarch I use SCI to evaluate Comb templates, so sci/eval-string will get called quite often with the same string (the template code) and different options (same namespace setup and load-fn, but different data). As I now added a load-fn to include namespaces from the "template library" I wonder, what the impact on performance is and if it can be optimized. I'm using a memoized load function so loading the namespaces is cached. But parsing will happen inside of SCI and I suppose there is no caching over the many calls to eval-string?!?

borkdude 2026-06-15T13:35:38.606169Z

I you need to execute parsed code, you can evaluate a function and call that function over and over?

borkdude 2026-06-15T13:36:17.804199Z

:load-fn will only be called for namespaces that were not already loaded

Ludger Solbach 2026-06-15T13:38:47.383849Z

For example I have a node.md.cmb template, which is called to generate the markdown documentation for every node in the architecture model (could be 1000 nodes). So 1000 calls to eval-string.

borkdude 2026-06-15T13:39:37.503899Z

are these strings or already clojure forms?

Ludger Solbach 2026-06-15T13:40:51.099009Z

yes, I parse each template once, so without the load-fn, I was not too worried about the performance.

borkdude 2026-06-15T13:41:21.464839Z

A or B? yes

Ludger Solbach 2026-06-15T13:42:08.053289Z

A: Strings (currently)

borkdude 2026-06-15T13:42:42.011779Z

I don't see a way how to avoid parsing those strings then? perhaps caching works for your situation if the evaluations are pure and not stateful

borkdude 2026-06-15T13:43:05.856879Z

what you could do is cache the parsing and then use eval-form instead

borkdude 2026-06-15T13:43:20.401549Z

but only do this if you have measurable performance issues

Ludger Solbach 2026-06-15T13:44:40.260659Z

Does SCI expose a read-string function, which could be cached?

borkdude 2026-06-15T13:45:29.844699Z

yes, look at these three: https://github.com/babashka/sci/blob/master/API.md#sci.core/parse-next + eval-from + reader

👀 1
borkdude 2026-06-15T13:47:01.906009Z

also using eval-string* is already better since you don't create a new context from the options all the time

borkdude 2026-06-15T13:48:23.314079Z

so first create a context ctx with sci/init , and a reader (from the string) then parse the string with (sci/parse-next ctx reader) or parse-string if you expect only one form

borkdude 2026-06-15T13:48:34.827099Z

and then evaluate that with eval-form

Ludger Solbach 2026-06-15T13:49:52.854289Z

I'm wrapping the complete template in a 'do' form anyway so there's one toplevel form. Calling parse-next will also load the required namespaces in the ctx or include them in the returned form?

borkdude 2026-06-15T13:50:45.696649Z

no, parse parses, it doesn't load anything

borkdude 2026-06-15T13:51:02.971029Z

can you give an example of how your expressions look

borkdude 2026-06-15T13:53:36.843859Z

the required namespaces will be evaluated when you eval-form the form

borkdude 2026-06-15T13:53:47.416509Z

makes sense, right?

Ludger Solbach 2026-06-15T13:54:42.937329Z

Yes, I suppose. And it will happen on each call of eval-form?

borkdude 2026-06-15T13:55:35.194159Z

I don't understand the question

Ludger Solbach 2026-06-15T13:56:38.171909Z

Each of the calls are independent and if there is no state for the loaded namespaces in the context, the require will need to load the namespaces many times.

borkdude 2026-06-15T13:57:01.548439Z

yes, therefore: use a ctx argument

Ludger Solbach 2026-06-15T13:57:49.253489Z

So the context manages/caches the namespaces?

borkdude 2026-06-15T13:58:30.315829Z

yes. also if you add those lib namespaces as a configuration on the context (or the options) in :namespaces there is nothing to load either

Ludger Solbach 2026-06-15T14:03:47.375589Z

I distinguish between the overarch code, which is added via the namespace key in options, and template code which could be changed by the user to customize the output of the generation. Overarch is distributed as a uberjar, so the overarch code is closed, but the template code is open and can be customized. I'd like to keep this separation because many users of overarch are not familiar with Clojure (yet).

borkdude 2026-06-15T14:05:08.557809Z

yes. makes sense. users can reload their code with (require '[foo] :reload) . otherwise it's cached.

Ludger Solbach 2026-06-15T14:05:34.198919Z

https://github.com/soulspace-org/overarch/blob/4f39cb994e083c2ba610958306dcfd934602704d/src/org/soulspace/overarch/adapter/template/comb.clj#L61 this is the current code for executing the templates with SCI.

Ludger Solbach 2026-06-15T14:07:02.665529Z

I really love SCI for providing a sandbox for executing the templates and it's currently very little code necessary to implement a template engine this way. Thanks for that!

❤️ 1
Ludger Solbach 2026-06-15T14:11:01.579699Z

As a contrast, this is a template engine I once implemented in Java for model driven software development: https://github.com/lsolbach/TemplateEngine. It's a halfbaked re-implementation of Lisp in a complex and error prone way. I'm glad, we're past this in the Clojure community. 😅

borkdude 2026-06-15T14:11:20.338579Z

hehe