2016-06-03 3 views
3

В течение некоторого времени я спрашивал себя, есть ли способ определить функцию с более чем одной вариационной перегрузкой. Вот пример функции я писал (я знаю, что нет никакого управления исключением, плюс, может быть, есть лучший способ, чтобы закодировать его -Вообще я не отлаживать его, - но я просто сосредоточиться здесь на VARIADIC аспекте):Clojure: более 1 вариационная перегрузка, более идиоматический способ сделать?

(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 вариационной перегрузки. Я предпочитаю его только потому, что он более понятен для конечного пользователя.

Я так учил о двух вещах:

  • с использованием второй собственной функции с применить ... но это не решает проблему в Teh первой isntance, потому что я хочу два возможных стилей входов.
  • Я огляделась defmulti, но я видел, что он также принимает те же арностей для каждого submethods

Конечно, я могу также разделить функции на два или два случая в первом арг (вектор с типами [ векторная карта] будет означать, что пользователь передает не форматированные данные + заголовок), но хуже для пользователя. Я действительно хочу предложить эти возможности ввода.

Есть ли что-то, что я не заметил в функциях clojure или это более глубокая проблема, которую мы не можем решить?

Спасибо!

+3

Вы можете передать все эти параметры ('sep dec newline') в качестве карты вместо пары ключ-значение. – OlegTheCat

+0

Возможно, но у меня будет проблема arity, которую я могу решить с defmulti. Таким образом, это решение, но теперь параметры находятся в хэшмапе, что менее интуитивно для старых пользователей python/r. Но все же это очень хорошая идея и, возможно, больше в стиле Clojure! –

ответ

2

Чтобы сохранить все ключевые слова args на отдельной карте, это лучшее решение, я думаю .. Но есть и один относительно популярный способ сделать это, если вам действительно нужно (чего я думаю нет): вы можете использовать один VARIADIC подписи в сочетании с 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 

язь вы (или пользователь вашей ОМТ) использовать будет показывать подпись :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*. 

Но опять же: это слишком многословно и трудно (из-за дублирования кода), поэтому вы должны использовать его с умом (под которым я подразумеваю, что никто не должен этого делать)

+0

Спасибо за ответ, который является более точным с учетом первоначального спроса. Но в конце вы правы, это немного уродливо. Я использую свое первое решение, даже если варианты карты, предложенные OlegTheCat, кажутся более «clojuresque» только потому, что это больше в духе моей библиотеки. Отладка Afer, моя функция работает. –