【问题标题】:Clojure : more than 1 variadic overload, more idiomatic way to do?Clojure : 超过 1 的可变参数重载,更惯用的方法是什么?
【发布时间】:2016-06-03 12:23:42
【问题描述】:

一段时间以来,我一直在问自己是否有一种方法可以定义一个具有多个可变参数重载的函数。 这是我编写的一个示例函数(我知道没有异常管理,也许还有更好的编码方式——实际上我没有调试它——但我只关注可变参数方面):

(defn write-csv
  "Writes a csv from data that is already formatted for clojure.data.csv/write-csv or not.
   In the second case, the function guesses a header and writes it. Can handle three types of
   data : nested rows (example : {1 {:a 2 :b 3} 2 {:a 25 :b 17} ...), flattened data (like the one you use
   in clj-data-process-utils.data (example : ({:id 1 :a 2 :b 3} {:id 2 :a 25 :b 17} ...)) or already formatted
   data (example : [['ID' 'B' 'C'] [1 2 3] [2 25 17]]). Note that in the last case you have to provide a header if you want one.
   The guesses can be overriden by the :header arg. Optimized for Excel with default values.
   Input :
   - data : data to write as CSV
   - path : the filepath of the new CSV
   - (optional) sep : the separator to use, must be of type char [default : ;]
   - (optional) dec : the decimal separator to use, must be of type char [default : .]
   - (optional) newline : the newline character, see cljure.data.csv options, default here for windows [default : :cr+lf]
   - (optional) header : if you want to provide your own data, pass here a vector of columns names, guesses by default if data is not formatted [default : :guess]"
  [data path & {:keys [sep dec newline header] :or {sep \; dec \. newline :cr+lf header :guess}}]
  (let [f-data (cond (or (map? data) (seq? data))
                       (cond (vec? header)
                               (format-for-csv sep data header)
                             (= :guess header)
                               (->> (guess-header data)
                                    (format-for-csv sep data)))
                     (vec? data)
                       data)
        wrtr (io/writer path)]
    (csv/write-csv wrtr f-data :separator sep :newline newline)))

如您所见,我们可以选择传递标头。我把它放在可选键上,但我宁愿在第一个实例中拥有这样的东西(即使这个 aritties 地图对我来说没问题):

(defn write-csv 
  ([data path & {:keys [sep dec newline] :or {sep \; dec \. newline :cr+lf}}]
    ...)
  ([data header path & {:keys [sep dec newline] :or {sep \; dec \. newline :cr+lf}}]
    ...))

当然它不起作用,因为我们不能有超过 1 个可变参数重载。我喜欢它只是因为它对最终用户来说更清楚。

我很担心两件事:

  • 将第二个私有函数与 apply...
  • 我查看了defmulti,但我发现每个子方法也需要相同的arities

当然,我也可以将函数拆分为两个或在第一个 arg 中区分两种情况(类型为 [vector map] 的向量意味着用户传递了未格式化的数据 + 标头),但对于用户。 我真的很想提供这些输入的可能性。

在 clojure 函数中有什么我没有注意到的,还是我们无法解决的更深层次的问题?

谢谢!

【问题讨论】:

  • 您可以将所有这些选项 (sep dec newline) 作为映射而不是键值对传递。
  • 可能,但我会有一个可以用 defmulti 解决的 arity 问题。所以这是一个解决方案,但现在选项在哈希图中,这对于老 python/r 用户来说不太直观。但它仍然是一个非常好的主意,也许更符合 Clojure 风格!

标签: clojure variadic-functions


【解决方案1】:

我想,将所有关键字 args 保存在单独的地图中是最好的解决方案。但如果你真的需要(我认为不是),还有一种相对流行的方法:你可以使用一个可变参数签名与arglists 元数据相结合:

user> (defn parse
        {:arglists '([data header? path & {:keys [sep dec newline]
                                           :or {sep :aaa
                                                dec :bbb
                                                newline :ccc}}])}
        [data header-or-path & args]
        (let [[header path {:keys [sep dec newline]
                            :or {sep :aaa dec :bbb newline :ccc}}]
              (if (even? (count args))
                [nil header-or-path args]
                [header-or-path (first args) (rest args)])]
          (println data header path sep dec newline)))
#'user/parse

user> (parse 1 2)
1 nil 2 :aaa :bbb :ccc
nil

user> (parse 1 2 3)
1 2 3 :aaa :bbb :ccc
nil

user> (parse 1 2 :sep :a :dec :b)
1 nil 2 :a :b :ccc
nil

user> (parse 1 2 3 :sep :a :dec :b)
1 2 3 :a :b :ccc
nil

您(或您的 lib 的用户)使用的 ide 将显示来自 :arglists 的签名,忽略真正的签名:

user/parse
 [data header? path & {:keys [sep dec newline], :or {sep :aaa, dec :bbb, newline :ccc}}]
  Not documented.
user/parse is defined in *cider-repl localhost*.

但同样:这太冗长且难以维护(由于代码重复),所以你应该明智地使用它(我的意思是根本没有人应该这样做)

【讨论】:

  • 感谢您的回答,考虑到最初的需求,这个答案更准确。但最后你是对的,这有点难看。我使用我的第一个解决方案,即使 OlegTheCat 提出的地图选项看起来更“clojuresque”只是因为它更符合我图书馆的精神。调试后,我的功能有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-29
  • 1970-01-01
  • 2011-08-13
  • 2011-05-04
  • 1970-01-01
  • 2011-03-07
相关资源
最近更新 更多