How to set a dynamic var before aot compile

本小妞迷上赌 提交于 2021-01-29 08:50:26

问题


I want to have a *flag* variable in a given namespace that is set to true only when :aot compiling.

Is there a way to do that?


回答1:


Your issue is kind of complicated because the definition of Clojure's own dynamic nature, so there's no rough equivalent of C's #ifdef or some other mechanism that happens at compile time, but here's a workaround:

I created a Leiningen project with lein new app flagdemo. This trick detects when AOT is performed, as @Biped Phill mentioned above, using the dynamic var *compile-files*, and saves a resource in the classpath of the compiled code:

The code looks like this:

(ns flagdemo.core
  (:gen-class))

(def flag-path "target/uberjar/classes/flag.edn")

(defn write-flag [val]
  (try (spit flag-path (str val)) (catch Exception _)))

(defn read-flag []
  (some-> (clojure.java.io/resource "flag.edn") slurp clojure.edn/read-string))

(write-flag false)

(when clojure.core/*compile-files*
  (write-flag true))

(defn -main
  [& args]
  (println "Flag is" (read-flag)))

So, when the file loads using, say, lein run or when you load it in the REPL, it will try to write an EDN file with the value false.

When you compile the package using lein uberjar, it loads the namespace and finds that *compile-files* is defined, thus it saves an EDN file that is packaged with the JAR as a resource.

The function read-flag just tries to load the EDN file from the classpath.

It works like this:

$ lein clean
$ lein run
Flag is nil

$ lein uberjar
Compiling flagdemo.core
Created /tmp/flagdemo/target/uberjar/flagdemo-0.1.0-SNAPSHOT.jar
Created /tmp/flagdemo/target/uberjar/flagdemo-0.1.0-SNAPSHOT-standalone.jar

$ java -jar target/uberjar/flagdemo-0.1.0-SNAPSHOT-standalone.jar
Flag is true



回答2:


Credit to @Biped Phill and @Denis Fuenzalida for explaining it to me.

I think this works as well:

(def ^:dynamic *static* false)

(when (and *compile-files* boot/*static-flag*)
  (alter-var-root #'*static* true))

then in profiles.clj:

:injections [(require 'boot)
             (intern 'boot '*static-flag* true)]



回答3:


First you need to indicate that a variable will change over time:

(def ^:dynamic *the-answer* nil)
*the-answer*
;=> nil

The ^:dynamic bit will allow that variable to be rebound later:

(binding [*the-answer* 42] *the-answer*)
;=> 42

ADDENDUM 1

Here's what would happen if you didn't use ^:dynamic:

(def *the-answer* nil)
(binding [*the-answer* 42] *the-answer*)
;=> [...] Can't dynamically bind non-dynamic var [...]

ADDENDUM 2

These kind of variables are commonly referred to as "earmuffs". The naming convention is unenforced but strongly encouraged.

Here's what happen in the REPL when you use this naming without declaring a dynamic variable:

(def *the-answer* nil)
; Warning: *the-answer* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *the-answer* or change the name. (/tmp/form-init7760459636905875407.clj:1)


来源:https://stackoverflow.com/questions/60647682/how-to-set-a-dynamic-var-before-aot-compile

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!