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

时间:2017-05-11 00:28:45

标签: clojure macros code-generation

更新:

我重写这篇文章是为了让原始问题更加清晰,并添加了全宏解决方案。请忽略此版本并参考以下内容:

How to create Clojure `defn` functions automatically without macros?

不幸的是,SO不允许我删除这个旧版本。

最初的动机是以下问题:Mapped calls to clojurescript macro

假设您想要自动创建许多类似的功能(即无需手动编写所有功能)。假设我们有一些预先存在的数据,我们想要编写这样的访问器函数:

(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])  

我们如何自动生成这些功能?

2 个答案:

答案 0 :(得分:2)

  

我们如何自动生成这些功能?

你不需要。 foobar地图作为您想要的功能运行:

(foo :able) ; "Adelicious!"

(bar :able) ; "Apple"

答案 1 :(得分:-4)

虽然您无法将map与宏一起使用,但您可以编写第二个宏来执行此功能。反过来,这可能需要编写第三个宏等,这是Clojure for the Brave and True和其他地方所描述的短语“Macros All Way Down”的起源。

使用Clojure的intern函数进行类似的问题was answered here。我们的问题与该问题略有不同,因为我们在这里以两种不同的方式使用intern

  • 创建与defdefn
  • 类似的全局变体
  • 使用var-get
  • 访问全局变量的值

为减少输入,我们将使用spyspy-let from the Tupelo library。您需要project.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-foomy-bar分别可以访问全局foobar地图。我们不需要使用宏!

在Clojure中,var有点像foomy-foo之类的符号之间的不可见“中间人”和他们指向的值(分别是地图和函数)例)。有关详细信息,请参阅相关帖子:

When to use a Var instead of a function?