Welcome! Please see the About page for a little more info on how this works.

+1 vote
ago in Syntax and reader by

TIL:

(eval (list + 1 2)) ; works
(eval (list (var-get #'+) 1 2)) ; works
(eval (list (fn [a b] (+ a b)) 1 2)) ; works

(defmacro m [] `(~(fn [a b] (+ a b)) 1 2))
(m) ; also works

;; however, if the function has any metadata:
(eval (list (with-meta + {}) 1 2))
;; ^ fails, No matching ctor found for class clojure.lang.AFunction$1

This was unintuitive to discover.

I solved it locally with a little helper:

(defn val->expr [v]
  (if (symbol? v)
    `'~v
    (cond-> v
      (and (fn? v) (not (nil? (meta v))))
      (-> (with-meta nil)
          (list (-> v meta (update-vals val->expr)))
          (conj `with-meta)))))

But this takes special handling. I wonder if something similar would not be possible on a "matching ctor"-level, possibly also for comp, every-pred?, etc. It would be fairly trivial even in userland to have versions of these functions which carry metadata sufficient to recreate expressions, e.g. comp storing the list of functions, and then eval, on encountering No matching ctor found for class clojure.core$comp$fn__5921, can instead pull out the list of functions from that value and evaluate (list* comp (:composed-fns (meta v))) (or some such) instead.

I think this would be great to have in the core lib, at least for metadata and core libs functions like comp, some-fn, every-pred?, and complement.

Note, I'm not talking about adding or changing ctors for these necessarily. A hook for taking unconstructable values and making a constructable value out of them would serve just as well, maybe even a multifn.

Strictly in userland and without core lib support, I suppose the cleanest way to do something like this is a custom protocol and probably monkeypatch some core libs functions (maybe eval?)

So I suppose my questions are:

  1. Is this something you agree would be good to have in general? Or is this by design for some reason I can't see?
  2. Would this be good and practical to have in the corelib? Oughtn't be a breaking change at least
  3. If no, is there currently any sane way to apply my solution in a more systemic way than wrapping every relevant value with a val->expr call?

1 Answer

0 votes
ago by

You're eval'ing + to a function object there when it seems like you should be staying in symbol land:

user=> (eval (list (with-meta '+ {}) 1 2))
3

A function object itself is not Clojure data (other than opaquely as an object). When working symbolically, you should stay in symbols.

There are some places where the compiler uses an escape hatch of print + read-eval (#=) to recover custom types (sorted maps and sets), but I'd consider that implementation details, not something you as a user should be doing generally.

ago by
Importantly, I'm *not* working symbolically. The specific pattern I'm trying to illustrate:

    ((eval      (with-meta (fn [] 5) nil))) ;; works, note `nil` meta
    (eval (list (with-meta (fn [] 5) nil))) ;; likewise

    ((eval      (with-meta (fn [] 5) {}))) ;; works, note `{}' meta
    (eval (list (with-meta (fn [] 5) {}))) ;; fails, no matching ctor found for class clojure.lang.AFunction$1

I encountered this working on somewhat `meta`-heavy function generators for a system where functions can opt-in to certain behaviors with their meta, but don't necessarily have meta, and are not necessarily symbolic/`def`ined. This works a treat, except when trying to `eval` it or embedding it in a backtick template.

 This required me to use my little `val->expr` helper, which also works a treat. Clojure knows how to evaluate function values, just as long as they don't have meta, but if a function-expr `f` has meta `m`, Clojure also knows how to evaluate `(with-meta (with-meta f nil) (meta f))

As to your last point, I agree entirely and that's kinda what I was trying to say. Manually doing the `(with-meta (with-meta f nil) (meta f))` trick every time I embed a function in case it has any metadata feels like handling Clojure implementation details that I shouldn't generally be worried about. Thus my question.

My more general question was, if this is possible, why not generalise it to other function-values (which, regardless of metadata, would be AFunctions and have no constructors) by implementing some fallback mechanism where, if a value (e.g. an AFunction) has (a) no constructor and (b) meta with some special key containing a function which takes the value and returns a Clojure expression-to-be-evaluated, e.g. the meta case, and likewise for `every-pred` and `complement` etc. to eval as a list with their name and args.

I'll post better/more concrete examples tomorrow, but for today I noticed another maybe-related strange-behavior/bug:
    (def f1 (eval +))
    (def f2 (eval (list `with-meta + nil)))
    (def f3 (eval (list `identity +)))
    (def f4 (eval (list `var-get (list `var `+))))

All of these evaluate to `#function[clojure.core/+]`. However:

    (= f1 f2) ;=> true
    (= f1 f4) ;=> true
    ;; however
    (= f1 f3) ;=> false

So in summary:

    ;; works
    ((eval      (with-meta (fn []) {})))
    ;; throws error (but shouldn't?)
    (eval (list (with-meta (fn []) {})))

    ;; true
    (=    + (eval +))
    ;; not true (but should be?)
    (= + (eval (list `identity +)))

Not really sure where to proceed to with these. If these were solved by the corelib, I as a user wouldn't need to be doing this.

A couple questions:

a) Where can I read more about `#=`? I couldn't find documentation for it
b) Why does `(eval (list `identity +))` return a different value to `+`? At the very least it feels unintuitive that they wouldn't be `=` even if not `identical?`, but why wouldn't they be identical?
ago by
#= is undocumented because it is not intended to be used outside the compiler.
...