Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you find all the free variables in a Clojure expression?

How do you find all the free variables in a Clojure expression?

By free variables, I mean all the symbols that are not defined in the local environment (including all local environments defined inside the expression), not defined in the global environment (all symbols in the current namespace, including all those imported from other namespaces), and not a Clojure primitive.

For example, there is one free variable in this expression:

(fn [ub]
  (* (rand-int ub) scaling-factor))

and that is scaling-factor. fn, *, and rand-int are all defined in the global environment. ub is defined in the scope that it occurs within, so it's a bound variable, too (i.e. not free).

I suppose I could write this myself—it doesn't look too hard—but I'm hoping that there's some standard way to do it that I should use, or a standard way to access the Clojure compiler to do this (since the Clojure compiler surely has to do this, too). One potential pitfall for a naïve implementation is that all macros within the expression must be fully expanded, since macros can introduce new free variables.

like image 828
Ben Kovitz Avatar asked Jan 31 '18 01:01

Ben Kovitz


People also ask

What are free type variables?

In computer programming, the term free variable refers to variables used in a function that are neither local variables nor parameters of that function.

What is a Var in Clojure?

Advertisements. In Clojure, variables are defined by the 'def' keyword. It's a bit different wherein the concept of variables has more to do with binding. In Clojure, a value is bound to a variable.

What is a free variable in Haskell?

Basically a free variable is a variable used in a lambda that is not one of the lambda's arguments (or a let variable). It comes from outside the context of the lambda. Eta reduction means we can change: (\x -> g x) to (g) But only if x is not free (i.e. it is not used or is an argument) in g .

What is a free variable in Javascript?

A free variable is simply a variable which is not declared inside a given function, but is used inside it.


1 Answers

You can analyze the form with tools.analyzer.jvm passing the specific callback to handle symbols that cannot be resolved. Something like this:

(require '[clojure.tools.analyzer.jvm :as ana.jvm])

(def free-variables (atom #{}))

(defn save-and-replace-with-nil [_ s _]
  (swap! free-variables conj s)

  ;; replacing unresolved symbol with `nil`
  ;; in order to keep AST valid
  {:op :const
   :env {}
   :type :nil
   :literal? true
   :val nil
   :form nil
   :top-level true
   :o-tag nil
   :tag nil})

(ana.jvm/analyze
 '(fn [ub]
    (* (rand-int ub) scaling-factor))
 (ana.jvm/empty-env)
 {:passes-opts
  (assoc ana.jvm/default-passes-opts
         :validate/unresolvable-symbol-handler save-and-replace-with-nil)})

(println @free-variables) ;; => #{scaling-factor}

It will also handle macroexpansion correctly:

(defmacro produce-free-var []
  `(do unresolved))

(ana.jvm/analyze
 '(let [x :foo] (produce-free-var))
 (ana.jvm/empty-env)
 {:passes-opts
  (assoc ana.jvm/default-passes-opts
         :validate/unresolvable-symbol-handler save-and-replace-with-nil)})

(println @free-variables) ;; => #{scaling-factor unresolved}
like image 176
OlegTheCat Avatar answered Sep 21 '22 08:09

OlegTheCat