如何在没有宏的情况下自动创建Clojure`defn`函数? [英] How to create Clojure `defn` functions automatically without macros?

查看:97
本文介绍了如何在没有宏的情况下自动创建Clojure`defn`函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最初由以下问题引起:映射到clojurescript宏的调用

Originally motivated by the following question: Mapped calls to clojurescript macro

假设您要自动创建许多相似的功能(即无需全部手写).假设我们有一些预先存在的函数,我们想用处理程序将它们包装起来,以进行某种回调:

Suppose you want to create many similar functions automatically (i.e. without hand-writing them all). Suppose we have some pre-existing functions and we want wrap them with handlers for a callback of some sort:

(defn do-foo [] (println "I foo'ed"))
(defn do-bar [] (println "I bar'ed"))
(defn do-baz [] (println "I baz'ed"))

(defn manual-on-foo [] (do-foo))
(defn manual-on-bar [] (do-bar))
(defn manual-on-baz [] (do-baz))

(println "Calling manual-on-* functions")
(manual-on-foo)
(manual-on-bar)
(manual-on-baz)

有结果:

Calling manual-on-* functions
I foo'ed
I bar'ed
I baz'ed

我们希望自动而不是手动生成包装器功能.

We want to generate the wrapper functions automatically instead of manually.

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

You might think you need a macro to create this function, and that is one solution. However, a weakness of macros is that they cannot be passed as arguments to another function such as map. Thus, we could write a macro like:

(generate-fn :foo)  ;=> creates `on-foo` w/o hand-writing it

,但以下操作将失败:

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

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

How can we automate the generation of these functions?

推荐答案

概述

虽然不能将map与宏一起使用,但是可以编写第二个宏来执行此功能.反过来,这可能需要编写第三个宏,依此类推,这是

Overview

While you can't use map with a macro, you could write a second macro to perform this function. This may, in turn, require writing a third macro, etc, which is the origin of the phrase "Macros All the Way Down" as described in Clojure for the Brave and True and other places.

使用Clojure的intern函数在在此处回答了.我们的问题与该问题略有不同,因为在这里我们以两种不同的方式使用intern:

A similar question was answered here by using Clojure's intern function. Our problem is a little different than that question, since here we use intern in two different ways:

  • 使用defdefn
  • 创建全局变量
  • 使用var-get
  • 访问全局变量的值
  • To create a global var like with def or defn
  • To access the value of a global var using var-get

使用intern允许我们编写以下代码,以在不使用宏的情况下自动生成on-*函数:

Using intern allows us to write the following code to automatically generate the on-* functions without using macros:

(defn generate-onstar-f
  [event-kw]
  (let [
    event-str (name event-kw)
    do-fn-sym (symbol (str "do-" event-str))
    on-fn-sym (symbol (str "on-" event-str))
    new-fn    (fn on-fn-sym []
                (let [the-var (intern 'tst.clj.core do-fn-sym) ; get the var the symbol 'do-fn-sym' points to
                      the-fn  (var-get the-var) ] ; get the fn the var is pointing to
                  (the-fn))) ]
    (intern 'tst.clj.core on-fn-sym new-fn) ; create a var 'on-fn-sym' pointing to 'new-fn'
    (println "Created" on-fn-sym)))

(println \newline "*** generating functions ***")
(mapv generate-onstar-f [:foo :bar :baz]) ; creates and interns a functions:  my-foo, my-bar, my-baz

(println \newline "Calling automatically generated on-* functions")
(on-foo)
(on-bar)
(on-baz)

有结果:

 *** generating functions ***
Created on-foo
Created on-bar
Created on-baz

 Calling automatically generated on-* functions
I foo'ed
I bar'ed
I baz'ed

因此,我们看到我们创建了函数on-fooon-bar& on-baz,依次称为全局do-foodo-bar和& do-baz功能.而且我们不需要使用宏!

So we see that we created the functions on-foo, on-bar & on-baz which, in turn, call the global do-foo, do-bar, & do-baz functions. And we didn't need to use macros!

在Clojure中,var有点像on-foo这样的符号与其所指向的值(在此示例中为函数)之间的不可见中间人".有关更多信息,请参见相关文章:

In Clojure, the var is somewhat of an invisible "middle-man" between a symbol like on-foo and the value it points to (a function in this example). For more information please see the relate post:

何时使用Var而不是函数?

如前所述,可以使用宏调用另一个宏,从而避免了宏不能与map之类的高阶函数(HOF)一起使用的问题.在这里,我们定义了一个新的宏run-macro,以替换generate-onstar-f不能使用的map HOF:

As mentioned previously, one could use a macro to invoke another macro, side-stepping the problem that macros can't be used with higher-order-functions (HOF) like map. Here we define a new macro run-macro, to replace the map HOF we can't use with generate-onstar-f:

(defmacro generate-onstar-m
  [event-kw]
  (let [event-str (name event-kw)
        do-fn-sym (symbol (str "do-" event-str))
        on-fn-sym (symbol (str "on-" event-str "-m"))]
    (println "Creating" on-fn-sym)
    `(defn ~on-fn-sym []
       (~do-fn-sym))))

(println \newline "Using Macro")
(generate-onstar-m :foo)
(on-foo-m)

(defmacro run-macro
  "Run the specified macro once for each arg"
  [root-macro args]
  `(do
    ~@(forv [item args]
      `(~root-macro ~item))))

(println \newline "Generating on-*-m functions using `run-macro`")
(run-macro generate-onstar-m [:foo :bar :baz])
(on-foo-m)
(on-bar-m)
(on-baz-m)

有结果:

 Using Macro
Creating on-foo-m
I foo'ed

 Generating on-*-m functions using `run-macro`
Creating on-foo-m
Creating on-bar-m
Creating on-baz-m
I foo'ed
I bar'ed
I baz'ed

这篇关于如何在没有宏的情况下自动创建Clojure`defn`函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆