Home > database >  How to pass a symbol to a function to create a function in clojure
How to pass a symbol to a function to create a function in clojure

Time:09-13

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

CodePudding user response:

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

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

CodePudding user response:

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.

CodePudding user response:

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
  • Related