【问题标题】:clojure - how (and when) to invoke the dot operator?clojure - 如何(以及何时)调用点运算符?
【发布时间】:2015-06-02 13:08:52
【问题描述】:

更新

这个问题在很大程度上是重复的,但下面的问题和我的回答似乎更具说明性。上一个问题见Access Java fields dynamically in Clojure?。


我正在尝试从 Clojure 查找 Java 对象实例中的字段设置。点运算符似乎在我天真地期望它起作用的地方失败了。

例如,有了这些定义......

(defn example-point []
  (let [instance (java.awt.Point. 1 2)]
     (list (. instance x)  (. instance y))))

(defn example-point-1 []
  (let [instance (java.awt.Point. 1 2)
        fields '("x" "y")]
    (map #(. instance (symbol %))
         fields)))

(defn example-point-2 []
  (let [instance (java.awt.Point. 1 2)
        fields (map symbol '("x" "y"))]
    (map (fn [field] (eval `(. ~instance ~field)))
         fields)))

我得到这些返回值:

flood.core> (example-point)

(1 2)

这很好,但是如果我想“以编程方式”提供字段的名称怎么办?这就是其他功能应该做的事情。以我天真的思维方式,它们应该返回与上面相同的值。但它们都给出了不同的错误。

flood.core> (example-point-1)

IllegalArgumentException 找不到匹配方法:类 java.awt.Point clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:53) 的符号

flood.core> (example-point-2)

CompilerException java.lang.RuntimeException: Can't embed object in code, may not defined print-dup: java.awt.Point[x=1,y=2], compile:(/tmp/form-init1213427540543573506. clj:1:5659)

我卡住了,你能帮我解决这个问题吗?


进一步更新

我确实尝试过,但它又产生了另一个错误。

;; added in response to suggestion...
(defn example-point-3 []
  (let [instance (java.awt.Point. 1 2)
        fields (map symbol '("x" "y"))]
    (map (fn [field] (eval `(. instance ~field)))
         fields)))

flood.core> (example-point-3)

CompilerException java.lang.RuntimeException: No such var: flood.core/instance, 正在编译:(/tmp/form-init1213427540543573506.clj:1:5659)

【问题讨论】:

    标签: java clojure quote


    【解决方案1】:

    以下代码示例以独立的方式重现了 Joost Diepenmaat 在Access Java fields dynamically in Clojure? 的回答中的中心思想。在实际使用中,他的java-fields 包在Clojars 和on Github 上,并提供了一个fields 函数,可以应用于Java 类的实例。

    (import java.lang.reflect.Field)
    (import java.lang.reflect.Modifier)
    
    (defn example-point* [] 
      (let [fields (filter #(let [m (.getModifiers %)]
                              (and (not (= 0 (bit-and Modifier/PUBLIC m)))
                                   (= 0 (bit-and Modifier/STATIC m))))
                           (.getDeclaredFields java.awt.Point))
            instance (java.awt.Point. 1 2)]
        (map #(.get % instance) fields)))
    

    即使对于包含一个静态私有字段的点来说,这种过滤也是必要的,这可以通过评估 (map #(identity %) (.getDeclaredFields java.awt.Point)) 看出。

    但是,如果我们从公共字段的已知名称开始,则以下较短的答案有效,并且更接近原始问题的精神。这又需要java.lang.reflect.Field。

    (defn example-point** [] 
      (let [field-names '("x" "y")
            instance (java.awt.Point. 1 2)]
        (map #(.get (.getField java.awt.Point %) instance) field-names)))
    

    请注意,这里根本没有使用点运算符。

    【讨论】:

      【解决方案2】:

      第一个错误是 clojure 评估 following form 的结果:

      (. instance-expr (method-symbol args*))
      

      如果您将列表作为. 特殊形式的第二个参数传递,它将被解释为对带有参数args 的方法method-symbol 的调用(即不计算(method-symbol args*))。

      在第二种情况下,您不需要引用 instance。由于 clojure 评估 instance 符号并以语法引用的形式替换它的值,因此您的情况的结果类似于:

      (. <instance of point> x)
      

      然后传递给 eval。 Eval 期望 clojure 代码进行评估,因为实例不正确(. 必须有一个符号作为第一个参数)。

      一种解决方案是不要在 example-point-2 中取消引用 instance

      【讨论】:

      • 我添加了一个变体example-point-3——但这也会产生错误。由于某种原因,编译器没有看到let 表单绑定的instance。
      • 你说得对,我在尝试的时候一定是全局定义了instance。
      • soulcheck,我已经整理了一个单独的工作答案。它最终根本不使用点运算符。我仍然不完全确定为什么上述. 变体无法工作。
      • @JoeCorneli 我认为它们无法正常工作主要是因为. 是一种具有特殊评估规则的特殊形式,它期望在某些地方(即符号和列表)中的某些事物并以不同的方式评估它们(而且很友好可悲的是,它需要一个列表,而不是 clojure 中的其他所有内容 - 如果没有,您的第一种方法将起作用)。如果 eval 考虑到本地绑定,第三个版本会起作用,但它不会 - 它评估全局上下文中的表达式。
      • 很好地呼吁使用反射!
      猜你喜欢
      • 2017-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-21
      • 2017-02-24
      相关资源
      最近更新 更多