Nero gris
Nero gris

Reputation: 582

How to pass a symbol to a function to create a function in clojure

As a minimal example of what I want to do:

(defn mkfn [func]
  (fn func [a] (print "I am a function")))
(mkfn 'x) ; => #function[user/mkfn/func--10871]
(type x)
(x)

The last two both result in:

Syntax error compiling at (conjure-log-12628.cljc:1:1).
Unable to resolve symbol: x in this context

I'm not sure why this doesn't work since fn takes symbols as input and 'x is a symbol. I'm also not sure how to accomplish this task.

For that matter:

user=> (def (eval 'y) 3)
Syntax error compiling def at (conjure-log-12628.cljc:1:1).

user=> (def 'y 3)
Syntax error compiling def at (conjure-log-12628.cljc:1:1).
First argument to def must be a Symbol

First argument to def must be a Symbol
user=> (type 'y)
clojure.lang.Symbol

Other things that don't work:

(defn mkfn [func]
  (fn (sympol func) [a] (print "i am a function")))

(symbol "y") ; => y ; a symbol
(def (symbol "y") 3) ; => an err

Upvotes: 1

Views: 337

Answers (3)

Martin Půda
Martin Půda

Reputation: 7568

You will probably need a macro. It seems that you want to call that function by the provided name, so you also have to replace fn with defn.

And you have to be careful about a number of arguments, because function x with argument vector [a] must be called with one argument, and not like (x).

(defmacro mkfn [func]
  `(defn ~func [~'a] 
     (print "I am a function")))

(mkfn x)
=> #'user/x

(x 1)
I am a function=> nil

There is also other way, using intern, so you can completely avoid writing macros:

(intern *ns* 'x (fn [a] (print "I am a function")))
=> #object...

(x 1)
I am a function=> nil

Example with intern:

(defn mkfn [func]
  (intern *ns* func (fn [a] (print "I am a function"))))
=> #'user/mkfn

(mkfn 'y)
=> #'user/y

(y 1)
I am a function=> nil

As for your errors, def is a special form, so it has different evaluation rules. It doesn't evaluate the first argument, which has to be a symbol- and (unevaluated) (eval 'y), 'y or (symbol "y") aren't symbols, while y is.

Upvotes: 1

Alan Thompson
Alan Thompson

Reputation: 29958

There 2 ways of doing it, either function plus eval or macro. If you really want to programatically create a new function with your chosen name, the macro solution is the way to go.

The function + eval solution is instructive, but you'll have to either quote the function name when you call it (via a 2nd eval) or save the created function in another variable when you create it.

If you are interested in writing macros, please see this other question first: How do I write a Clojure threading macro?

For the function + eval, we can start with my favorite template project and add the following:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(defn maker-eval
  [fn-sym]
  (let [ll (list 'fn 'anon-fn [] (str "I am " fn-sym))]
    (spyx :eval ll)
    (eval ll)))

(verify
  (let [anon-fn-1 (maker-eval 'aaa)]
    (is= "I am aaa" (anon-fn-1))) ; need the temp local variable
  (let [anon-fn-2 (maker-eval 'bbb)]
    (is= "I am bbb" (anon-fn-2))) ; need the temp local variable
  )

and we can see the creation and use of the function, along with printed output:

:eval ll => (fn anon-fn [] "I am aaa")
:eval ll => (fn anon-fn [] "I am bbb")

For the macro version, we type

(defn maker-macro-impl
  [fn-sym]
  (let [ll `(defn ~fn-sym [] (str "I am " (str (quote ~fn-sym))))]
    (spyx :macro ll)
    ll))

(defmacro maker-macro
  [fn-sym] (maker-macro-impl fn-sym))

(verify
  (let [anon-fn-3 (maker-macro-impl 'ccc)]
    (is= anon-fn-3 (quote
                     (clojure.core/defn ccc [] (clojure.core/str "I am " (clojure.core/str (quote ccc)))))))
  (maker-macro ddd)
  (is= (ddd) "I am ddd"))

and see printed:

:macro ll => (clojure.core/defn ccc [] (clojure.core/str "I am " (clojure.core/str (quote ccc))))

Note that the local variable anon-fn-3 was only used to test the maker-macro-impl function, but was not needed to call the newly-created function ddd at the end of the unit test.

Upvotes: 0

Eugene Pakhomov
Eugene Pakhomov

Reputation: 10652

You gonna need a macro for that since you need code writing code.

(defmacro mkfn [func]
  `(fn ~func [~'a] ...))

Upvotes: 0

Related Questions