Clojure:创建函数,其名称在运行时作为参数给定



我正在尝试创建一个Clojure函数,该函数将返回另一个具有自定义名称的函数。到目前为止我的尝试:

(defn function-with-custom-name [name] (fn name [] 42))
(function-with-custom-name "hello")
# --> #object[lang.main$function_with_custom_name$name__4660 0xa6afefa "lang.main$function_with_custom_name$name__4660@a6afefa"]
# syntactically ok, but its name is 'name' and not 'hello'
(defn function-with-custom-name [name] (fn (symbol name) [] 42))
# --> CompilerException java.lang.IllegalArgumentException: Parameter declaration symbol should be a vector, compiling:(/tmp/form-init3365336074265515078.clj:1:40)
(defn function-with-custom-name [name] (fn '(symbol name) [] 42))
# --> CompilerException java.lang.IllegalArgumentException: Parameter declaration quote should be a vector, compiling:(/tmp/form-init3365336074265515078.clj:1:40)

我知道fn是一个宏,因此正确的引用可能对参数很重要,但如上所述,我无法正确理解,但我99%相信有办法,因为(查看fn的来源),唯一的标准是第一个参数应该被识别为符号。

有关于如何做到这一点的提示吗?

EDIT:用例,正如评论中所问:我正在用Clojure编写一个简单的语言解释器,它(除其他外)允许您创建函数。我的语言中的函数目前由匿名的Clojure函数表示。但是,如果Clojure函数也有一个名称,那么调试解释器会容易得多。

EDIT2:第一次编辑让我思考,我得出的结论是,我不能使用基于宏的解决方案来解决这个问题,因为我需要在运行时创建函数(而且,据我所知,宏只能在编译时工作)。-->为清楚起见,更改了问题标题。不过,请不要删除基于宏观的答案,因为它们提供了有用的见解。

您可以使用defmacro。

(defmacro function-with-custom-name [name] 
`(fn ~(symbol name) ([] 42)))

您也可以在运行时执行此操作,而不使用宏,而是使用命名空间函数。例如,它可以为您提供从一些输入注册函数的方法(尽管我真的找不到任何好的理由,但可能只有我)

user> (defn runtime-defn [f-name f]
(intern (ns-name *ns*) (symbol f-name) f))
#'user/runtime-defn
user> (runtime-defn "my-fun" #(* 100 %))
#'user/my-fun
user> (my-fun 123)
;;=> 12300
user> (runtime-defn (read) #(* 200 %))
#input "danger!!!"
#'user/danger!!!
user> (danger!!! 1)
;;=> 200

更新

对于简单版本,可以使用defmacro。对于更复杂的版本(例如potemkin库使用的版本),您需要创建一个var并将其"插入"到clojure命名空间中。这是通过clojure.core/intern:实现的

(ns tst.demo.core
(:use demo.core tupelo.test )
(:require [tupelo.core :as t] ))
(t/refer-tupelo)
(defmacro  make-fn-macro-unquote [name]
(spyxx name)
`(defn ~name [] 42))
(defn  make-fn-func-quote [name2]
(spyxx name2)
(intern 'tst.demo.core (symbol name2) (fn [] 43)))
(dotest
(make-fn-macro-unquote fred)
(spyxx fred)
(is= 42 (spyx (fred)))
(let [wilma-var (make-fn-func-quote "wilma")]
(spyxx wilma-var)
(is= 43 (spyx (wilma-var)))))

查看输出:

name => clojure.lang.Symbol->fred
fred => tst.demo.core$fn__38817$fred__38818->#object[tst.demo.core$fn__38817$fred__38818 0x5f832a1 "tst.demo.core$fn__38817$fred__38818@5f832a1"]
(fred) => 42
name2 => java.lang.String->"wilma"
wilma-var => clojure.lang.Var->#'tst.demo.core/wilma
(wilma-var) => 43

请注意,fred是一个clojure函数,而wilma-var是一个clojure变量。请参阅这篇关于像foo这样的符号与变量和函数之间关系的文章。

还请注意,宏版本采用未加引号的符号fred作为输入,而函数版本采用双引号的纯字符串作为输入。

因此,fred是指向函数的符号,而wilma-var是指向var(然后指向函数)的符号。在任何一种情况下,Clojure都允许我们键入(fred)(wilma-var)来进行函数调用,结果得到42或43。

最新更新