All Projects → borkdude → dynaload

borkdude / dynaload

Licence: EPL-1.0 license
The dynaload logic from clojure.spec.alpha as a library

Programming Languages

clojure
4091 projects
shell
77523 projects

dynaload

Clojars Project

Dynaload functionality inspired by clojure.spec.alpha.

This library works in:

  • Clojure JVM
  • Clojure JVM + GraalVM native-image
  • ClojureScript
  • Babashka

Why

It's often useful to make libraries light-weight and transitive dependencies optional. For example, clojure.spec.alpha makes test.check optional in both the Clojure and ClojureScript implementations. This project extracts that logic and packages it as a library.

Usage

This library exposes one namespaces: borkdude.dynaload with one macro: dynaload. The macro returns a delay that will either contain a value or will throw upon deref (unless provided a :default in a map in the second argument). It lets you dynamically refer to a var that may or may not be there. In Clojure it will require the namespace for you and throw if the namespace is not there. In ClojureScript you will have to require the namespace manually before deref, since ClojureScript namespaces cannot be loaded dynamically (outside of a REPL).

GraalVM

When using this library with GraalVM native-image it is recommended to set the Java property borkdude.dynaload.aot to true both during Clojure compilation and GraalVM native-image compilation. This will avoid using require at runtime, which has a beneficial effect on binary size and compile time memory usage. An example can be found in graal-test which shows these differences in binary size:

With sci.core borkdude.dynaload.aot=true binary size in MB
yes no 33
no no 25
yes yes 17
no yes 8

Because setting borkdude.dynaload.aot to true will avoid runtime require, you will have to require the dynaloaded namespaces before namespaces where the vars are dynaloaded.

Options

In addition to a fully qualified symbol, dynaload accepts an option map with currently one options: :default, a value that is returned if the var cannot be found. If no default is provided, dynaload will throw instead.

Example

Consider this example from examples/sci.cljc

(ns example.sci
  (:require
   [borkdude.dynaload :refer [dynaload]]))

(def eval-string (dynaload 'sci.core/eval-string))

(println (eval-string "(+ 1 2 3)"))

First we run this without having the sci library on the classpath.

On the JVM:

$ clojure example/sci.cljc
Syntax error (FileNotFoundException) compiling at (example/sci.cljc:8:1).
Var sci.core/eval-string does not exist, sci.core never required

ClojureScript:

$ plk example/sci.cljc
Execution error (Error) at (<cljs repl>:1).
Var sci.core/eval-string does not exist, sci.core never required

And now we load it with sci on the classpath.

JVM:

$ clj -Sdeps '{:deps {borkdude/sci {:mvn/version "0.1.0"}}}' example/sci.cljc
6

ClojureScript:

$ plk -Sdeps '{:deps {borkdude/sci {:mvn/version "0.1.0"}}}' -e "(require '[sci.core])" example/sci.cljc
6

Note: in ClojureScript we had to require sci manually, whereas in Clojure it was required for us.

Test

$ script/test

License

Copyright © 2020 - 2022 Michiel Borkent

Distributed under the EPL License. See LICENSE.

This project is based on code from:

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].