Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to decompose a Clojure function?

While I may incorrectly interpret the concept of homoiconicity, I've understood it as 'code being data'.

So, I can write code like this:

(def subject "world")
(def helo '(str "Hello " subject))

At this point, helo is only data, but can be executed as code like this:

(eval helo)

which returns "Hello world".

I can also continue to treat helo as data:

(first helo)
(count helo)

which returns respectively str and 3.

So far so good. However, as soon as I wrap the code in a function, I seem to lose the ability to treat code as data:

(defn helofn [subject]
  (str "Hello " subject))

How do I decompose helofn? It seems that I can't treat it as data; if I do this:

(count helofn)

I get an exception:

java.lang.UnsupportedOperationException: count not supported on this type: user$helofn

Is there another way to decompose helofn, or am I just expecting too much from homoiconicity?

like image 770
Mark Seemann Avatar asked May 23 '13 15:05

Mark Seemann


People also ask

What is destructuring in clojure?

What is Destructuring? Destructuring is a way to concisely bind names to the values inside a data structure. Destructuring allows us to write more concise and readable code. Consider the following example of extracting and naming values in a vector.

How do you define a list in Clojure?

List is a structure used to store a collection of data items. In Clojure, the List implements the ISeq interface. Lists are created in Clojure by using the list function.


3 Answers

The helofn definition is data, but you're letting it be evaluated (just as you explicitly evaluated the helo list). If you treated the definition in the same way as helo, then it will remain data, and amenable to whatever transformations you want to apply:

(def helofndata '(defn helofn [subject]
                   (str "Hello " subject))

=> (second helofndata)
helofn
=> (eval helofndata)
#'user/helofn
like image 94
cemerick Avatar answered Oct 23 '22 10:10

cemerick


defn is just a macro:

(macroexpand '(defn helofn [subject]
  (str "Hello " subject)))

(def helofn (clojure.core/fn ([subject] (str "Hello " subject))))

If you define helofn the way you defined helo, you'll be able to treat it as data:

(def helofn '(fn [subject]
  (str "Hello " subject)))

Now you can eval and call this function:

((eval helofn) "world")

and to treat it as a data:

(count helofn)

But, when you use defn macro you associates helofn variable with compiled function and not with it's code.

It's not just functions. Let's say you defined hello with the following code:

(def helo (str "Hello " subject))

Now hello is associated with "Hello world" string and not with (str "Hello " subject) code. So, now there is no way to get the code this string was built with.

N.B. If you want to treat clojure code as data you should look into its macros. Any code passed to a macro is treated as data and any data returned by a macro is treated as code.

like image 36
Leonid Beschastny Avatar answered Oct 23 '22 10:10

Leonid Beschastny


Homoiconicity is a very powerful concept and I don't think you are expecting too much from it.

defn is actually a macro that uses the def special form to define a function, so:

(defn sq [x]
  (* x x))

Is actually equivalent to:

(def sq (fn ([x] (* x x))))

So defn here is receiving the args sq [x] (* x x), then builds the list (def sq (fn ([x] (* x x)))), returns it as the result of the macro and is then eval'ed. This is all done through the manipulation of lists, maps, vectors, symbols, etc., by the defn macro.

The fact that in Clojure you can't get the original list of symbols from which you defined a function, has to do with the fact that in Clojure all code is compiled. This is why evaluating (fn [x] 1) in the REPL returns something like #<user$eval809$fn__810 user$eval809$fn__810@10287d> . But still, as mentioned in a previous answer, the code that is evaluated is data.

Maybe I'm going too far with this, but if you wanted to have for each function you define, the data from which it was created, you could add it to its metadata by creating your own custom macro.

Here's a naive implementation for such a macro:

(defmacro defn* [x & body ]
  (let [form `'~&form
        x    (vary-meta x assoc :form form)]
    `(defn ~x ~@body)))
;=> #'user/defn*

(defn* sq [x]
  (* x x))
;=> #'user/sq

(:form (meta #'sq))
;=> (defn* sq [x] (* x x))

&form is an implicit argument (together with &env) that contains the whole (unevaluated) form with which the macro was called (i.e. the data that is evaluated by the compiler).

Hope this helps and it doesn't bring more confusion.

like image 4
juan.facorro Avatar answered Oct 23 '22 08:10

juan.facorro