【问题标题】:Clojure Metaprogramming Question (for a beginner!)Clojure 元编程问题(适合初学者!)
【发布时间】:2010-07-06 12:09:21
【问题描述】:

大家好,我开始研究 Clojure 语言,并且对我正在尝试做的事情有几个问题。广泛的目标是将序列函数every? 别名为all?。我确定有一个函数或宏可以做别名(或类似的东西),但我想看看到目前为止我所知道的一些基本结构是否可行。我的方法是定义一个名为all? 的函数,将其参数应用于every? 实现。

我很想知道这是否可以变得不可知,所以我想为我的别名函数提供参数以获取两个参数,新名称(作为关键字)和旧名称(作为函数引用)。在朝着这个目标努力的过程中,我遇到了两个问题。

1) 使用关键字定义命名函数会引发错误。显然它想要clojure.lang.IObj

user=> (defn :foo "bar")     
java.lang.ClassCastException: clojure.lang.Keyword cannot be cast to clojure.lang.IObj (NO_SOURCE_FILE:0)

是否有将关键字转换为 IObj 的函数,或其他方法来参数化具有某些提供值的新定义函数的名称? (在 Ruby 中,define_method 以及其他技术可以做到这一点)

irb(main)> self.class.instance_eval do
irb(main)* define_method(:foo) { "bar" }
irb(main)> end
=> #<Proc>
irb(main)> foo
=> "bar"

2) 将函数的所有参数收集到一个变量中。甚至像(+ 1 2 3 4) 这样的基本函数也接受可变数量的参数。到目前为止,我所看到的所有函数定义技术都采用特定数量的参数,无法将列表中的所有内容聚合起来以便在函数体中进行处理。再一次,我要在 Ruby 中完成,如下所示:

irb(main)> def foo(*args)
irb(main)> p args
irb(main)> end
=> nil
irb(main)> foo(1, 2, 3)
[1, 2, 3]
=> nil

感谢您为我提供的任何帮助!

【问题讨论】:

    标签: clojure


    【解决方案1】:

    我会用要点来回答,因为这些问题可以整齐地分成许多单独的问题。

    • 隐含在后面的内容中,但可能需要自己的子弹:由def & Co.(尤其是defn)创建的顶级对象是瓦尔斯。所以你真正想做的是给一个 Var 起别名;函数只是没有真正名称的常规值(除了它们可能在其体内本地绑定到自己的名称;但这与手头的问题无关)。

    • Clojure 中确实有一个“别名宏”可用——clojure.contrib.def/defalias:

      (use '[clojure.contrib.def :only [defalias]])
      (defalias foo bar)
      ; => foo can now be used in place of bar
      

      (def foo bar) 相比,它的优势在于它复制了元数据(例如文档字符串);它甚至似乎可以与当前 HEAD 中的宏一起使用,尽管我记得在早期版本中有一个错误阻止了它。

    • 变量由符号命名,而不是关键字。 Clojure(和其他 Lisps)中的符号文字不以冒号开头(:foo 是关键字,而不是符号)。因此,要定义一个名为foo 的函数,您应该编写

      (defn foo [...] ...)
      
    • defn 是一个辅助宏,通过允许程序员混合使用deffn 语法来简化new 函数保持变量的创建。因此,defn 对于创建具有预先存在的值(可能是函数)的 Var 是不可能的,因为这是创建别名所必需的;请改用defalias 或简单的def

    • 要创建可变参数函数,请使用以下语法:

      (fn [x y & args] ...)
      

      xy 将是位置参数;传递给函数的其余参数(任意数量)将被收集到一个序列中,并以 args 的名称提供。如果不需要,您不必指定任何“必需的位置参数”:(fn [&amp; args] ...)

      要创建一个包含可变参数函数的 Var,请使用

      (defn foo [x y & args] ...)
      
    • 要将函数应用于您已组装到可序列对象中的某些参数(例如上述示例中的args seq 或向量等),请使用apply

      (defn all? [& args]
        (apply every? args))
      
    • 如果你想编写一个 函数 来创建别名——而不是宏——你需要研究函数internwith-meta、@987654347 @ -- 可能还有resolve / ns-resolve,这取决于函数是接受符号还是变量。我将把填写细节作为练习留给读者。 :-)

    【讨论】:

    • 刚刚发布了一个带有intern-alias 函数的要点,实现了上一段中暗示的设计。我会在一秒钟内添加一个文档字符串。它将元数据从原始复制到别名,支持在当前命名空间之外的命名空间中创建别名,接受符号和变量作为原始名称 &c。见gist.github.com/464970
    • 来吧,伙计。至少给别人一个机会!难道没有什么药可以让你的智力恢复到正常吗?回到我们身边,兄弟,我们想念你。 :p
    • 感谢您的深入评论。它绝对照亮了我正在寻找的大部分内容。关于第 3 点(变量由符号命名,而不是关键字),有没有办法将关键字转换为符号?比如说,为了练习,我想写一个函数来包装 def,并接受两个参数,一个关键字和一个值,然后在内部调用 def 并将变量命名为关键字的名称。可能吗?
    • @Evan:你不能用def 而不使用eval,因为def 需要一个文字符号作为它的第一个参数。更好地使用intern(它提供了将def 打包为一个函数的功能,特别是它评估它的参数,所以你可以说(intern *ns* (symbol (name :foo)) initial-value))。至于关键字符号强制,可以使用函数symbol从字符串创建符号,使用函数name获取关键字/符号的名称,使用函数keyword从字符串和符号创建关键字;见(doc symbol) &c。了解详情。
    • @Rayne:谢谢你的好意。我会努力成为应得的。 :-)
    【解决方案2】:

    您需要做的就是绑定every?功能齐全?符号,通过def完成:

    (def all? every?)
    

    更多信息,请参阅Clojure macro to create a synonym for a function

    【讨论】:

    • 我正在探索实现这样的功能,但我也很高兴知道快速的方法,谢谢!
    【解决方案3】:

    不要认为我可以在此处对现有解释添加太多内容,除非在 Ruby Traveller's dictionary 中填写有关参数收集和解构的几个空白:

    (defn foo [& args]                 ; Ruby: def foo(*args)
      (println args))    
    user=> (foo 1 2 3)
    (1 2 3)
    
    (defn foo [& args] 
      (+ args))   
    user=> (foo 1 2 3)
    java.lang.ClassCastException       ; + takes numbers, not a list
    
    (defn foo [& args] 
      (apply + args))                  ; apply: as Ruby proc.call(*args)
    user=> (foo 1 2 3)
    6
    
    (defn foo [& args]
      (let [[a b & other] args]        ; Ruby: a, b, *other = args
        (println a b other)))
    user=> (foo 1 2 3)
    1 2 (3)
    

    【讨论】:

      猜你喜欢
      • 2023-03-27
      • 1970-01-01
      • 2011-11-07
      • 1970-01-01
      • 2010-09-17
      • 2015-08-17
      • 2015-02-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多