如何自动创建Clojure`defn`函数? 更新:

如何自动创建Clojure`defn`函数? 更新:,clojure,macros,code-generation,Clojure,Macros,Code Generation,我重新编写了这篇文章,以使原来的问题更清楚,并添加了所有宏解决方案。请忽略此版本,并参考以下内容: 很遗憾,SO不允许我删除此旧版本 最初出于以下问题: 假设您希望自动创建许多类似的函数(即,不必全部手写)。假设我们有一些预先存在的数据,并希望编写如下访问器函数: (def foo {:able "Adelicious!" :baker "Barbrallicious!" :charlie "Charlizable"}) (def bar {:able

我重新编写了这篇文章,以使原来的问题更清楚,并添加了所有宏解决方案。请忽略此版本,并参考以下内容:

很遗憾,SO不允许我删除此旧版本


最初出于以下问题:


假设您希望自动创建许多类似的函数(即,不必全部手写)。假设我们有一些预先存在的数据,并希望编写如下访问器函数:

(def foo
  {:able    "Adelicious!"
   :baker   "Barbrallicious!"
   :charlie "Charlizable"})
(def bar
  {:able    "Apple"
   :baker   "Berry"
   :charlie "Kumquat"})

(defn manual-my-foo [item] (get foo item))
(defn manual-my-bar [item] (get bar item))

(manual-my-foo :able) => "Adelicious!"
(manual-my-bar :charlie) => "Kumquat"
因此
manual my foo
是“硬连线”的,可以使用
foo
全局地图

您可能认为需要一个宏来创建此函数,这是一种解决方案。但是,宏的一个缺点是它们不能作为参数传递给另一个函数,如
map
。因此,我们可以编写如下宏:

(generate-fn :foo)  ;=> creates `my-foo` w/o hand-writing it
但以下措施将失败:

(map generate-fn [:foo :bar :baz])  

我们如何自动生成这些函数?

虽然不能对宏使用
map
,但可以编写第二个宏来执行此函数。反过来,这可能需要编写第三个宏等,这是短语“宏一直向下”的来源,如和其他地方所述

通过使用Clojure的
intern
函数,可以得到一个类似的问题。我们的问题与那个问题稍有不同,因为这里我们用两种不同的方式使用
intern

  • 使用
    def
    defn
  • 使用
    var-get
为了减少键入,我们将使用
spy
spy let
。在您的
项目中需要以下内容。clj

[tupelo "0.9.38"]
这允许我们编写以下代码:

(ns tst.clj.core
  (:use clj.core )
  (:require [tupelo.core :as t] ))
(t/refer-tupelo)

(def foo
  {:able    "Adelicious!"
   :baker   "Barbrallicious!"
   :charlie "Charlizable"})
(def bar
  {:able    "Apple"
   :baker   "Berry"
   :charlie "Kumquat"})

(defn generate-event-fn
  [event-kw]
  (spy-let [
    event-str (name event-kw)
    event-sym (symbol event-str)
    fn-name   (symbol (str "my-" event-str))
    new-fn    (fn fn-name [item]
                (let [the-var (intern 'tst.clj.core event-sym) ; get the var the symbol 'event-sym' points to
                      the-map (var-get the-var) ; get the map the var is pointing to
                      the-str (get the-map item)] ; get the string from the map
                  (spyx the-var)
                  (spyx the-map)
                  (spyx the-str)))
  ]
    (intern 'tst.clj.core fn-name new-fn) ; create a var 'fn-name' pointing to 'new-fn'
  ))

(newline)  (println "*** generating functions ***")
(newline)  (generate-event-fn :foo) ; creates and interns a function 'my-foo'
(newline)  (generate-event-fn :bar) ; creates and interns a function 'my-bar'

(newline)  (println "*** calling function ***")
(newline)  (spyx (my-foo :able))
(newline)  (spyx (my-bar :charlie))
执行时,我们会看到以下结果:

*** generating functions ***

event-str => "foo"
event-sym => foo
fn-name => my-foo
new-fn => #object[tst.clj.core$generate_event_fn$fn_name__32477 0x1ebd2a1b "tst.clj.core$generate_event_fn$fn_name__32477@1ebd2a1b"]

event-str => "bar"
event-sym => bar
fn-name => my-bar
new-fn => #object[tst.clj.core$generate_event_fn$fn_name__32477 0x4824083 "tst.clj.core$generate_event_fn$fn_name__32477@4824083"]

*** calling function ***

the-var => #'tst.clj.core/foo
the-map => {:able "Adelicious!", :baker "Barbrallicious!", :charlie "Charlizable"}
the-str => "Adelicious!"
(my-foo :able) => "Adelicious!"

the-var => #'tst.clj.core/bar
the-map => {:able "Apple", :baker "Berry", :charlie "Kumquat"}
the-str => "Kumquat"
(my-bar :charlie) => "Kumquat"
因此,我们创建了函数
my foo
my bar
,分别可以访问全局
foo
bar
地图。我们不需要使用宏

在Clojure中,var在某种程度上是介于
foo
my foo
等符号和它们所指向的值(本例中分别是一个映射和一个函数)之间的一个看不见的“中间人”。有关更多信息,请参阅相关帖子:

我们如何自动生成这些函数

你不需要。
foo
bar
贴图可按您所需的功能操作:

(foo :able) ; "Adelicious!"

(bar :able) ; "Apple"

仅供参考,在Stackoverflow中推广您自己的库(除非确实需要答案)是不受欢迎的:(,)我本可以使用
println
,而不是
spy
&
spy let
,但这会增加重复和混乱。你认为
println
会更好吗?我只是看不到在原始问题中有人问“如何调试/打印中间值”。如果有一天出现调试问题,您可以提出tupelo库。否则,请回答问题而不使用调试代码。而FWIW,一个简单的内联
(def xx the val)
是我在调试时所做的一切,因为
数据>打印字符串
这是对另一个问题的回应:用户在哪里尝试自动生成回调函数。我只是试图简化成一个更通用的函数。也许我在这里过于简单了。我可能错了,但我真的不认为最初的动机是为地图中的关键字编写访问器——这只是一个过于简单的例子。当然,在这个例子中,复杂的解决方案是不必要的,但我不想用10段的篇幅来解释复杂性,因为我问了一个简单的问题。这最初是因为这个问题,用户试图在CLJS中自动生成一些回调函数:我认为使用
intern
而不是宏调用宏是一种错误很好的解决方案。