Macros Clojure range案例宏
在R.Kent Dybvig的书中,作者在第86页为接受条件范围的Macros Clojure range案例宏,macros,clojure,case,Macros,Clojure,Case,在R.Kent Dybvig的书中,作者在第86页为接受条件范围的case语句编写了定义语法(Scheme宏)。我想在Clojure试试这个 结果如下 我该如何改进这一点?我对范围运算符使用:ii,:ie,:ei,和:ee,表示包含-包含,包含-排除,包含-排除, 分别为独家和独家。有更好的选择吗 我选择扩展到cond语句,而不是离散的if语句,因为我觉得我将从cond宏的任何未来改进中获益 (defmacro range-case [target & cases] "Compare
case
语句编写了定义语法
(Scheme宏)。我想在Clojure试试这个
结果如下
我该如何改进这一点?我对范围运算符使用:ii
,:ie
,:ei
,和:ee
,表示包含-包含,包含-排除,包含-排除,
分别为独家和独家。有更好的选择吗
我选择扩展到cond
语句,而不是离散的if
语句,因为我觉得我将从cond
宏的任何未来改进中获益
(defmacro range-case [target & cases]
"Compare the target against a set of ranges or constant values and return
the first one that matches. If none match, and there exists a case with the
value :else, return that target. Each range consists of a vector containing
3 terms: a lower bound, an operator, and an upper bound. The operator must
be one of :ii, :ie, :ei, or :ee, which indicate that the range comparison
should be inclusive-inclusive, inclusive-exclusive, exclusive-inclusive,
or exclusive-exclusive, respectively.
Example:
(range-case target
[0.0 :ie 1.0] :greatly-disagree
[1.0 :ie 2.0] :disagree
[2.0 :ie 3.0] :neutral
[3.0 :ie 4.0] :agree
[4.0 :ii 5.0] :strongly-agree
42 :the-answer
:else :do-not-care)
expands to
(cond
(and (<= 0.0 target) (< target 1.0)) :greatly-disagree
(and (<= 1.0 target) (< target 2.0)) :disagree
(and (<= 2.0 target) (< target 3.0)) :neutral
(and (<= 3.0 target) (< target 4.0)) :agree
(<= 4.0 target 5.0) :strongly-agree
(= target 42) :the-answer
:else :do-not-care)
Test cases:
(use '[clojure.test :only (deftest is run-tests)])
(deftest unit-tests
(letfn [(test-range-case [target]
(range-case target
[0.0 :ie 1.0] :greatly-disagree
[1.0 :ie 2.0] :disagree
[2.0 :ie 3.0] :neutral
[3.0 :ie 4.0] :agree
[4.0 :ii 5.0] :strongly-agree
42 :the-answer
:else :do-not-care))]
(is (= (test-range-case 0.0) :greatly-disagree))
(is (test-range-case 0.5) :greatly-disagree)
(is (test-range-case 1.0) :disagree)
(is (test-range-case 1.5) :disagree)
(is (test-range-case 2.0) :neutral)
(is (test-range-case 2.5) :neutral)
(is (test-range-case 3.0) :agree)
(is (test-range-case 3.5) :agree)
(is (test-range-case 4.0) :strongly-agree)
(is (test-range-case 4.5) :strongly-agree)
(is (test-range-case 5.0) :strongly-agree)
(is (test-range-case 42) :the-answer)
(is (test-range-case -1) :do-not-care)))
(run-tests)"
`(cond
~@(loop [cases cases ret []]
(cond
(empty? cases)
ret
(odd? (count cases))
(throw (IllegalArgumentException.
(str "no matching clause: " (first cases))))
(= :else (first cases))
(recur (drop 2 cases) (conj ret :else (second cases)))
(vector? (first cases))
(let [[lower-bound operator upper-bound] (first cases)
clause (second cases)
[condition clause]
(case operator
:ii `((<= ~lower-bound ~target ~upper-bound) ~clause)
:ie `((and (<= ~lower-bound ~target)
(< ~target ~upper-bound)) ~clause)
:ei `((and (< ~lower-bound ~target)
(<= ~target ~upper-bound)) ~clause)
:ee `((< ~lower-bound ~target ~upper-bound) ~clause)
(throw (IllegalArgumentException.
(str "unknown operator: " operator))))]
(recur (drop 2 cases) (conj ret condition clause)))
:else
(let [[condition clause]
`[(= ~target ~(first cases)) ~(second cases)]]
(recur (drop 2 cases) (conj ret condition clause)))))))
(定义宏范围案例[目标和案例]
“将目标与一组范围或常量值进行比较,然后返回
第一个匹配的。如果不匹配,则存在
值:否则,返回该目标。每个范围由一个包含
3个术语:下限、运算符和上限。运算符必须
是:ii、:ie、:ei或:ee中的一个,表示范围比较
应该是包容的,包容的,排斥的,排斥的,,
或者分别是独家的。
例子:
(射程情况目标)
[0.0:ie 1.0]:非常不同意
[1.0:ie 2.0]:不同意
[2.0:ie 3.0]:中性
[3.0:ie 4.0]:同意
[4.0:ii 5.0]:强烈同意
42:答案是什么
:否则:不在乎)
扩展到
(续)
(和(一些想法:
- 为操作符设置一个默认值(例如:ie在典型问题中可能是最自然的)
- 将其中一个边界默认为上一个或下一个上限/下限,这样就不需要重复相同的边界值
- 考虑ifs而不是cond,这样您就可以进行区间二分法(如果您期望非常多的情况,这将是一个性能胜利)
另一种方法是使宏在案例级别工作,如下所示:
(cond
(in-range target [0.0 1.0]) :greatly-disagree)
(in-range target [1.0 2.0]) :disagree)
...)
我个人喜欢这样做,因为如果需要,您可以将范围测试与其他谓词混合使用。我也会选择更详细但不太难看的内容
(range-case target
[(<= 0.0) (< 1.0)] :greatly-disagree
[(<= 1.0) (< 2.0)] :disagree
[(<= 2.0) (< 3.0)] :neutral
[(<= 3.0) (< 4.0)] :agree
(<= 4.0 5.0) :strongly-agree
42 :the-answer
:else :do-not-care)
(范围案例目标
[(我对它的最初看法:
(defn make-case [test val]
(if (vector? test)
`((and ~@(for [[lower comp upper] (partition 3 2 test)]
(list comp lower upper)))
~val)
(list :else val)))
(defmacro range-case [& cases]
(let [cases (partition 2 cases)]
`(cond ~@(mapcat (partial apply make-case) cases))))
这需要对语法稍作修改,如下所示:
(range-case
[0.0 <= x < 1.0] :greatly-disagree
[1.0 <= x < 2.0] :disagree
[2.0 <= x < 3.0] :neutral
[3.0 <= x < 4.0] :agree
[4.0 <= x <= 5.0] :strongly-agree
[42 = x] :the-answer
:else :do-not-care)
@米凯拉:这和常规的cond
:-)一样冗长。不过,我喜欢你的前两条评论。我会进一步探讨它们。作为一个一般原则,我不认为尽量减少冗长是一个特别有用的目标……我会选择可读性、可维护性和一致性,而不仅仅是保留一些键入字符:-)@米凯拉:我同意。我是个聪明人。不过,你对(范围内…
的建议应该是另一个宏。它本身非常有用。@米凯拉:关于你对隐式下限的建议,在第一个case子句中,如果省略下限,它可以(隐式地)是(-Integer/MAX\u Integer)
(没有实际添加一个(<-Integer/MAX\u Integer)目标)
到不断增长的cond
)。我不确定我是否理解“区间二分法”“你提到过。@Ralph区间二分法只是意味着你先取中间的情况-如果目标值小于2.0,那么你根本不需要检查任何较高的情况,因为它们必须是假的。因此,你只需一次测试就可以消除一半的情况。它基本上将复杂性从O(n)变成O(logn)如果您有n个案例,那么如果n较大,则值得这么做。您的建议,再加上mikera对“默认”运算符和隐式下限的建议,可能会很有用。我们将进行调查。甚至可能会调查[LB-UB]
,[LB
,[
,以及[:ii
、:ie
、:ei
和:ee
,默认运算符为,谢谢。“所以我怀疑您是否真的需要宏。”这更像是编写宏的练习,而不是真正有用的东西。
(range-case
[0.0 <= x < 1.0] :greatly-disagree
[1.0 <= x < 2.0] :disagree
[2.0 <= x < 3.0] :neutral
[3.0 <= x < 4.0] :agree
[4.0 <= x <= 5.0] :strongly-agree
[42 = x] :the-answer
:else :do-not-care)
(defn ?? [& xs]
(every? (fn [[lower comp upper]]
(comp lower upper))
(partition 3 2 xs)))
(cond
(?? 0.0 <= x < 1.0) :greatly-disagree
(?? 1.0 <= x < 2.0) :disagree
(?? 2.0 <= x < 3.0) :neutral
(?? 3.0 <= x < 4.0) :agree
(?? 4.0 <= x <= 5.0) :strongly-agree
(= 42 x) :the-answer
:else :do-not-care)