【问题标题】:How to get name of argument in Clojure?如何在 Clojure 中获取参数名称?
【发布时间】:2012-08-08 01:40:44
【问题描述】:

我想从函数内部获取在函数外部定义的 var 的名称。该名称应该是我在原始定义范围内使用的名称,而不是我实际尝试使用该名称的任何嵌套绑定。

所以我想做一些类似(学术例子)的事情:

(defn f1 [x1] (println "hello, you passed var name >>" (get-var-name x1) "<<")
(defn f2 [x2] (f1 x2))
(defn f3 [x3] (let [zzz x3] (f2 zzz))
(def my-var 3.1414926)
(f3 my-var)
user> hello, you passed var name >>my-var<<

我可以根据我找到的一些东西来做这个宏:

(defmacro get-var-name [x]
  `(:name (meta (var ~x))))

这在例如从 REPL 调用时有效,但是当从“内部”范围调用时编译器会阻塞,例如

(defn another-func [y]
  (get-var-name y))

编译器说“无法解析 var y”。 (macroexpand...) 表明它试图在当前命名空间中查找局部变量 y,而不是当前命名空间中的原始变量。我认为(var...) 仅查找命名空间变量,因此这会阻止宏在函数或其他绑定(例如let)中工作。

认为我不得不从定义变量的同一范围内手动获取变量名,并将其作为额外参数传递。有没有更优雅的方法通过绑定链将 var 名称信息传递到使用它的位置?那会很糟糕。

谢谢

【问题讨论】:

    标签: clojure macros var


    【解决方案1】:

    不可能获得在外部作用域中使用的变量的名称​​在函数内 - 函数只接收在运行时作为参数传递的,不是 var 本身。

    您唯一可能做的就是在每个级别使用宏而不是函数。这允许您在编译时通过不同的宏传递 var 本身:

    (defmacro f1 [x1] `(println "hello, you passed var name >>" ~(str x1) "<<"))
    (defmacro f2 [x2] `(f1 ~x2))
    (defmacro f3 [x3] (let [zzz x3] `(f2 ~zzz)))
    
    (f3 my-var)
    => hello, you passed var name >> my-var <<
    

    这很丑 - 你当然不想为了获得这个功能而用宏编写所有代码!尽管在某些特殊情况下它可能是有意义的,例如如果您正在创建某种基于宏的 DSL。

    【讨论】:

    • 这个,一百倍。其他答案(到目前为止)与提问者的假设一致,即涉及 vars 并且实际工作,但事实并非如此(如问题中的最后一个示例所示)。
    【解决方案2】:

    您可以使用#'阅读器宏将实际的var而不是var解析的值传递给函数,如下所示:

    user=> (defn f1 [x1] (println "hello, you passed var name >>" (:name (meta x1)) "<<"))
    #'user/f1
    user=> (defn f2 [x2] (f1 x2))
    #'user/f2
    user=> (defn f3 [x3] (let [zzz x3] (f2 zzz)))  
    #'user/f3
    user=> (def my-var 3.1414926)
    #'user/my-var
    user=> (f3 #'my-var)
    hello, you passed var name >> my-var <<
    

    如果您希望将值绑定到 var,您可以使用 var-get 函数来执行此操作。

    【讨论】:

    • 谢谢,这个回复实际上引发了我对符号->vars->values 如何在 clojure 中工作的数小时调查。它适用于带有 vars 的 repl,但仍然不适用于 let-bounded 符号。
    【解决方案3】:

    为什么不直接将(get-var-name _symbol-here_) 传递给您希望在主体内使用var 名称的函数?例如,使用与您在上面给出的 get-var-namef2f3my-var 完全相同的定义(但稍微更改 f1):

    (defmacro get-var-name [x]
      `(:name (meta (var ~x))))
    (defn f1 [x1] (println "hello, you passed var name >>" x1 "<<"))
    (defn f2 [x2] (f1 x2))
    (defn f3 [x3] (let [zzz x3] (f2 zzz)))
    (def my-var 3.1414926)
    => (f3 (get-var-name my-var))
    hello, you passed var name >> my-var <<
    => nil
    

    也许有时候,你想在函数体中引用符号所引用的值。例如,假设在f1 中,您还想打印出该变量名称引用的值。你可以这样做:

    (defn f1 [x1] (println "hello, you passed var name >>" x1 "<<"
                           "\nwhich refers to value >>" @(resolve x1) "<<"))
    => (f3 (get-var-name my-var))
    hello, you passed var name >> my-var << 
    which refers to value >> 3.1414926 <<
    => nil
    

    注意@(resolve x1)——这是返回my-var所指的值的东西(而my-var又是x1所指的值)。

    另外,我想提一下,当传递给它的参数不是符号或者是符号但当前未绑定到值时,您当前的 get-var-name 实现将抛出异常。这是你想要的行为吗?

    如果我的建议不能回答您的问题,那么您似乎不想(get-var-name _symbol-here_) 传递给可能最终使用 var 名称的函数,而是出于某种原因,真的希望能够从函数体内执行(get-var-name ...)。如果是这种情况,为什么您希望能够那样做?或者,如果您觉得我出于其他原因没有回答您的问题,那是什么原因?

    【讨论】:

    • 谢谢,但我需要从函数体内获取变量名。基本上,我试图将更高范围的变量的名称附加到函数调用中几个级别的其他文本中。我想我可以在开始调用链之前用(binding...) 试试这个。
    • @Sonicsmooth 我想我明白了。我很想看看你正在做/尝试做的真实(即非学术示例)版本。
    • ...好吧,我也放弃了(binding...)。它需要在我的命名空间中添加一个额外的虚拟 var,让它变得丑陋。
    • 我正在尝试编写自己的数据库内部连接,其中每个表都是一组 clojure 映射;在一组地图共享密钥。 (我知道'clojure.set 中已经有一个连接)。但是如果这两个表有一个共同的键,其中一个值会在计算笛卡尔积的(天真)第一步中被破坏,所以我试图保持键的名称不同。目前我只是在前面加上“左”。和“对”。共享密钥名称,但我想改用 var 名称。我可以像在另一个响应中一样传递从 REPL 使用的 var,但我也希望传递一个 let-bound 符号。
    • @Sonicsmooth 我现在了解更多了,但老实说我对数据库操作并不熟悉,所以我觉得我的深度不够。但是,我认为如果您发布一个新问题,描述“在不丢失共享密钥的情况下进行数据库内部连接”的问题(或者您将其命名为),您可能会得到解决最终问题的解决方案,该解决方案可能涉及也可能不涉及获取嵌套范围内的 var 名称。
    猜你喜欢
    • 1970-01-01
    • 2020-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-18
    • 2018-01-29
    • 2010-09-18
    相关资源
    最近更新 更多