【问题标题】:Clojure: create function whose name is given as a parameter at run-timeClojure:创建函数,其名称在运行时作为参数给出
【发布时间】:2017-11-01 19:15:31
【问题描述】:

我正在尝试创建一个 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的出处,唯一的标准是第一个参数应该被识别为符号。

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

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

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

【问题讨论】:

  • 你能澄清一下这个用例吗(即为什么你在运行时创建 fn 以及如何使用它)?
  • @AlanThompson:见编辑。

标签: function clojure macros


【解决方案1】:

你可以使用 defmacro。

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

【讨论】:

    【解决方案2】:

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

    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
    

    【讨论】:

      【解决方案3】:

      更新

      对于简单版本,您可以使用defmacro。对于更复杂的版本(例如使用的by the potemkin library),您需要创建一个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 var。请see this post 了解foo 之类的符号与var 和function 之间的关系。

      另请注意,宏版本将不带引号的符号 fred 作为输入,而函数版本将双引号中的纯字符串作为输入。

      所以fred 是指向函数的符号,而wilma-var 是指向var(然后指向函数)的符号。无论哪种情况,Clojure 都允许我们输入 (fred)(wilma-var) 来进行函数调用,结果是 42 或 43。

      【讨论】:

      • dotestis= 是什么?
      • 它们是tupelo.test 中的便利函数(为清楚起见添加了命名空间声明)。
      • 谢谢,我尝试了您的解决方案,效果很好。您介意解释一下为什么它不适用于函数调用吗?
      • @Downvoter:你能解释一下这个答案有什么问题吗?有什么事实不正确吗? (赞成解释背景,尽管我承认我还没有完全理解它;)
      • 嗨 Attilio - 有些人甚至会否决正确的答案和被认为不够“纯粹”的工作代码。在这种情况下,我使用了我创建和维护的 Tupelo 库中的函数 dotestis=spyxspyxxgithub.com/cloojure/tupelo
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-02
      相关资源
      最近更新 更多