我只需要i
在算法中使用。对于这样的任务,我觉得导入clojure.math
是矫枉过正的。
为什么?
我不需要复杂的结果,实际上不需要带有虚部的实部。在我的实现中,我一次只使用一个值,并且从不将两者结合起来,除了乘法。输出最终没有参考i
,虚部只需要查看计算中符号的变化。
简而言之,如果有一种方法可以定义i
为:
(def i (....) )
这样(* i i)
等于-1
。
我只需要i
在算法中使用。对于这样的任务,我觉得导入clojure.math
是矫枉过正的。
为什么?
我不需要复杂的结果,实际上不需要带有虚部的实部。在我的实现中,我一次只使用一个值,并且从不将两者结合起来,除了乘法。输出最终没有参考i
,虚部只需要查看计算中符号的变化。
简而言之,如果有一种方法可以定义i
为:
(def i (....) )
这样(* i i)
等于-1
。
如果你想(* i i)
评估,-1
我们需要准备一个宏。
(ns user)
(defmacro *
[& args]
(let [i-count (count (filter #(= % 'i) args))
error #(throw (Exception. "Illegal number of imaginary units."))
i-factor (case (mod i-count 4)
0 1
2 -1
(error))]
`(clojure.core/* ~@(conj (filter #(not= % 'i) args) i-factor))))
宏扩展为普通乘法,它不应该干扰实数的乘法。
user=> (macroexpand '(* i i))
(clojure.core/* -1)
user=> (macroexpand '(* i i 5 i 6 i))
(clojure.core/* 1 5 6)
user=> (macroexpand '(* 1.3 3.7))
(clojure.core/* 1 1.3 3.7)
user=> (macroexpand '(* i (+ 2 3) i))
(clojure.core/* -1 (+ 2 3))
Is a macro necessary? Without a macro i
s present in (* i i
) would get evaluated. Since they weren't defined it would cause a compile-time error. As suggested in the question, we could define i
as a value which *
knows how to handle. Despite that being possible, it would still be evaluated at runtime. A clear advantage of a macro is the fact that it's evaluated during compilation and replaced with an ordinary call to clojure.core/*
as shown in examples above. Simply put, it's fast.
像这样的东西很快就被黑了*?
(defn hacked-* [& args]
(let [[i-amount product]
((juxt (comp count filter)
(comp #(apply * %) remove))
#{:i} args)]
(if (and (> i-amount 0) (even? i-amount))
(- product)
product)))
(hacked-* 1 2 3) => 6
(hacked-* 1 2 3 :i :i) => -6
(hacked-* 1 2 3 :i :i :i) => 6
您可以在词法上将 * 重新绑定到被破解的版本以评估复杂的表达式:
(let [* hacked-*]
(* 1 :i 2 :i 3 :i (* :i :i))) => -6
这是我的技巧,假设参数都是虚构的:
(defn im* [& i] ((fn [q n] (([* str * str] q) ([1 'i -1 '-i] q) n)) (mod (count i) 4) (reduce * i)))
...可怕!但是如果没有例如破解Numbers.java以调度到 java.lang.Number 的新子类型,您所要求的实际上是不可能的。实际上,使用 Java 或 Clojure 复数库会更有意义。