使用let样式解构为def

时间:2017-07-15 18:31:02

标签: clojure macros

是否有合理的方法让def语句与let相同的方式发生多个(let [[rtgs pcts] (->> (sort-by second row) (apply map vector))] .....) 语句?例如:

(defs [rtgs pcts] (->> (sort-by second row)
                       (apply map vector)))

我想要的是:

(defmacro def2 [[name1 name2] form] 
  `(let [[ret1# ret2#] ~form]
      (do (def ~name1 ret1#)
      (def ~name2 ret2#))))

这在REPL,笔记本和调试时出现了很多。认真地感觉就像一个缺失的功能,所以我想要一个

的指导
  • 这已经存在,我想念它
  • 这是一个坏主意,因为......(变量捕获?,非惯用??Rich这么说?)
  • 这是不必要的,我必须忍受从邪恶的语言中退出。 (同样如下:不要用你的宏搞乱我们的语言)

一个超短的实验给我一些类似的东西:

(def2 [three five] ((juxt dec inc) 4))
three ;; => 3
five ;; => 5

这就像:

csv

当然,该宏的“工业实力”版本可能是:

  • 检查名称数量是否与输入数量相匹配。 (从表格返回)
  • 递归调用以处理更多名称(我可以在这样的宏中执行此操作吗?)

2 个答案:

答案 0 :(得分:4)

虽然我同意Josh的说法,你可能不应该让它在制作中运行,但我认为将它作为一个方便的副本并没有任何伤害(事实上我认为我会这样做)将其复制到我的debug-repl kitchen-sink库中)。

我喜欢编写宏(尽管通常不需要它们)所以我掀起了一个实现。它接受任何绑定表单,例如let

(我首先编写了这个规范,但是如果您使用clojure< 1.9.0-alpha17,您可以删除规范内容并且它的工作方式相同。)

(ns macro-fun
  (:require
   [clojure.spec.alpha :as s]
   [clojure.core.specs.alpha :as core-specs]))

(s/fdef syms-in-binding
  :args (s/cat :b ::core-specs/binding-form)
  :ret (s/coll-of simple-symbol? :kind vector?))

(defn syms-in-binding
  "Returns a vector of all symbols in a binding form."
  [b]
  (letfn [(step [acc coll]
            (reduce (fn [acc x]
                      (cond (coll? x) (step acc x)
                            (symbol? x) (conj acc x)
                            :else acc))
                    acc, coll))]
    (if (symbol? b) [b] (step [] b))))

(s/fdef defs
  :args (s/cat :binding ::core-specs/binding-form, :body any?))

(defmacro defs
  "Like def, but can take a binding form instead of a symbol to
   destructure the results of the body.
   Doesn't support docstrings or other metadata."
  [binding body]
  `(let [~binding ~body]
     ~@(for [sym (syms-in-binding binding)]
         `(def ~sym ~sym))))


;; Usage

(defs {:keys [foo bar]} {:foo 42 :bar 36})

foo ;=> 42

bar ;=> 36

(defs [a b [c d]] [1 2 [3 4]])

[a b c d] ;=> [1 2 3 4]

(defs baz 42)

baz ;=> 42

关于您的REPL驱动的开发评论:

我没有Ipython的任何经验,但我会简要解释一下我的REPL工作流程,你可以评论一下与Ipython的任何比较/对比。

我从不像终端那样使用我的repl,输入命令并等待回复。我的编辑器支持(emacs,但任何clojure编辑器应该这样做)将光标放在任何s表达式的末尾并将其发送到repl,"打印"光标后的结果。

我通常在我开始工作的文件中有一个comment块,只需键入任何内容并进行评估。然后,当我对结果感到满意时,我将其从" repl-area"进入"真实代码"。

(ns stuff.core)

;; Real code is here.
;; I make sure that this part always basically works,
;; ie. doesn't blow up when I evaluate the whole file

(defn foo-fn [x]
  ,,,)

(comment

  ;; Random experiments.

  ;; I usually delete this when I'm done with a coding session,
  ;; but I copy some forms into tests.

  ;; Sometimes I leave it for posterity though,
  ;; if I think it explains something well.

  (def some-data [,,,])

  ;; Trying out foo-fn, maybe copy this into a test when I'm done.
  (foo-fn some-data)

  ;; Half-finished other stuff.
  (defn bar-fn [x] ,,,)

  (keys 42) ; I wonder what happens if...

  )

您可以在clojure core source code中找到相关示例。

答案 1 :(得分:3)

任何一件clojure所具有的def的数量因项目而异,但我说一般来说,def s通常不是某些计算的结果,更不用说需要解构的计算结果了。更常见的是def s是的起点,用于稍后计算,这取决于此值。

通常函数更适合计算值;如果计算成本很高,那么你可以记住这个功能。如果你觉得你真的需要这个功能,那么一定要使用你的宏 - 这是clojure的销售点之一,即可扩展性!但总的来说,如果你觉得自己需要这种结构,那就考虑一下你过度依赖全球状态的可能性。

为了给出一些真实的例子,我刚刚在大约20个命名空间中引用了我的主要项目,这可能是2K-3K行的clojure。我们有大约20 def s,其中大多数都标记为私有,其中没有一个实际上是计算任何东西。我们有类似的事情:

(def path-prefix "/some-path")
(def zk-conn (atom nil))
(def success? #{200})
(def compile* (clojure.core.memoize/ttl compiler {} ...)))
(def ^:private nashorn-factory (NashornScriptEngineFactory.))
(def ^:private read-json (comp json/read-str ... ))

定义函数(使用compmemoize),枚举,通过atom状态 - 但没有真正的计算。

所以我要说,根据你的要点,这大概介于2到3之间:它绝对不是一个需要的常见用例(你是'我曾经听说过第一个想要这个的人,所以这对我来说并不常见);它不常见的原因是因为我上面所说的,即它可能是一种代码气味,表明依赖于过多的全球状态,因此,不会是非常惯用的。

我的大部分代码都有一个试金石:如果我把这个函数从这个命名空间中拉出来并粘贴到另一个中,它还能用吗?删除对外部变量的依赖性允许更容易的测试和更模块化的代码。有时我们需要它,所以看看你的要求是什么,并相应地进行。祝你好运!