【问题标题】:Dynamic variables in Clojure librariesClojure 库中的动态变量
【发布时间】:2018-04-28 19:23:08
【问题描述】:

TL;DR:以下是图书馆的好模式吗?

(def ^{:dynamic true} *var*)
(defn my-fn [{:keys [var]}]
  (do-smth (or var *var*)))

--

假设我想写一个情感分析库。

get-sentiment fn 中接受可选情感标签但提供默认情感标签作为动态变量是否是好的设计?

(def ^:dynamic *sentiment-level-labels*
  ["Very Negative" "Negative" "Neutral" "Positive" "Very Positive"])

;;...

(defn get-sentiment-scores
  "Takes text and gives back a 0 to 4 sentiment score for each sentences."
  [text]
  ;;...)

(defn get-sentiment 
  "Gives back a sentiment map with sentences scores, 
   average score, rounded score and labeled score.
   Can accepts custom sentiment level labels under :labels opt."
  [text & {:keys [labels]}]
  (let [scores (get-sentiment-scores text)
        average-score (get-average scores)
        rounded-score (Math/round average-score)
        label (get (or labels *sentiment-level-labels*) rounded-score)]
    {:scores scores
     :average-score average-score
     :rounded-score rounded-score
     :label label}))

Clojure 库编码标准官方页面说:

如果你提供一个隐式传递参数的接口 动态绑定(例如 sql 中的 db),也提供相同的接口 但是显式传递的参数。

https://dev.clojure.org/display/community/Library+Coding+Standards

在我的示例中,我只提供了一个接口,但带有 opt 参数。

这样好吗?有没有更好的方法来处理这个问题?

谢谢!

【问题讨论】:

  • 根据我的经验,使用动态参数 /almost/ 总是比节省下来的麻烦更多。你需要一个非常有说服力的理由来拥有它们,在这种情况下,我肯定会让*sentiment-level-labels* 成为非动态的default-sentiment-level-labels。请注意,clojure.java.jdbc(如文档中的示例)曾经有一个动态的默认 db 参数,但它在很久以前就被删除了。
  • 谢谢!我真的很喜欢“default-”前缀。
  • 在不关闭耳罩的情况下*sentiment-level-labels 是否也可以直观地标记该值存在于函数中时未传入参数?甚至*default-sentiment-level-labels?谢谢!
  • @leontalbot 不,那是超级恶心和奇怪的。没有人会那样使用*,所以没有人阅读你的代码会理解你希望传达的意思。
  • Stuart Sierra 对动态变量进行了很好的讨论。它的标题是“动态范围的危险”。你也许能猜到他的结论是什么。 :-) 链接:stuartsierra.com/2013/03/29/perils-of-dynamic-scope

标签: clojure


【解决方案1】:

动态变量是完整的还是有缺陷的。它们将您的 API 代码推向隐式环境耦合,并经常迫使您的调用代码添加许多 (binding ...) 子句,这首先违背了使用动态变量的简洁目的。如果控制从一个线程传递到另一个线程,它们还会导致棘手的边缘情况。

在您的情况下,我建议只需在 params 映射参数中传递标签:

(def default-sentiment-level-labels
  ["Very Negative" "Negative" "Neutral" "Positive" "Very Positive"])

(defn get-sentiment 
  "Gives back a sentiment map with sentences scores, 
   average score, rounded score and labeled score.
   Can accepts custom sentiment level labels under :labels opt."
  [text {:as params, :keys [labels] :or {labels default-sentiment-labels}}]
  ...))

请注意,map 的使用可能很有趣,因为 map 对中介是不透明的:您可以让算法的各种组件仅从 params 映射中读取与它们相关的键。

【讨论】:

  • 地图对中介是不透明的,是解决这个问题的好方法。但是,正如您在此处与 & {...} 一起使用的 unrolled 映射对于每个中介来说都是一个 PITA,因为它们必须从展开的关键字 args 构建一个映射,并将其解构为展开的关键字 args传递到下一层。简单地使用[text {:as params ...}] 而不是[text & {:as params ...}] 要好得多。
  • @amalloy 我的错误,我完全同意你关于展开地图的看法,并相应地更正了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-08
  • 2013-08-13
  • 1970-01-01
相关资源
最近更新 更多