sci 2026-06-15

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?!?

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

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

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.

are these strings or already clojure forms?

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

A or B? yes

A: Strings (currently)

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

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

but only do this if you have measurable performance issues

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

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

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

and then evaluate that with eval-form

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?

no, parse parses, it doesn't load anything

can you give an example of how your expressions look

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

makes sense, right?

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

I don't understand the question

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.

yes, therefore: use a ctx argument

So the context manages/caches the namespaces?

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

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).

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

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

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. 😅