Clojure: динамическое создание функций из карты Время для макроса?

У меня есть функция, которая начинается так:

(defn data-one [suser]
    (def suser-first-name
       (select db/firstNames
            (fields :firstname)
            (where {:username suser})))
    (def suser-middle-name
        (select db/middleNames
            (fields :middlename)
            (where {:username suser})))
    (def suser-last-name
         (select db/middleNames
             (fields :lastname)
             (where {:username suser})))
    ;; And it just continues on and on...
        )

Конечно, мне это совсем не нравится. У меня есть этот шаблон, повторяющийся во многих областях моей кодовой базы, и я хотел бы обобщить это.

Итак, для начала я придумал следующее:

(def data-input {:one '[suser-first-name db/firstNames :firstname] 
                      '[suser-middle-name db/middleNames :middlename]
                      '[suser-last-name db/lastNames :lastname]})

(defpartial data-build [data-item suser]
    ;; data-item takes the arg :one in this case
     `(def (data-input data-item)
        (select (data-input data-item)
            (fields (data-input data-item))
            (where {:username suser}))))

Тут действительно несколько вопросов:

-- Как я могу деконструировать ввод данных, чтобы он создавал функции x, когда x неизвестен, т.е. что значения :one неизвестны, и количество ключей во входных данных неизвестно.

-- Я думаю, что пришло время создать макрос, но я никогда раньше его не создавал, поэтому сомневаюсь в этой идее.

И чтобы дать небольшой контекст, функции должны возвращать значения для деконструкции, но я думаю, что как только я решу эту часть, можно будет обобщить все это:

(defpage "/page-one" []
    (let [suser (sesh/get :username)]       
    (data-one suser)
        [:p "Firat Name: " 
            [:i (let [[{fname :firstname}] suser-first-name]
                (format "%s" fname))]
        [:p "Middle Name: "  
            [:i (let [[{mname :emptype}] suser-middle-name]
                (format "%s" mname))]
        [:p "Last Name: " 
            [:i (let [[{lname :months}] suser-last-name]
                    (format "%s" lname))]]))

person dizzystar    schedule 29.09.2012    source источник


Ответы (2)


Некоторые предложения:

  • def внутри функции действительно неприятно — вы изменяете глобальную среду, и это может вызвать всевозможные проблемы с параллелизмом. Вместо этого я бы предложил сохранить результаты на карте.
  • Здесь вам не нужен макрос — все выборки данных можно относительно легко выполнить внутри функции.

Поэтому я бы предложил что-то вроде:

(def data-input [[:suser-first-name db/firstNames :firstname] 
                 [:suser-middle-name db/middleNames :middlename]
                 [:suser-last-name db/lastNames :lastname]])

(def data-build [data-input suser]
  (loop [output {}
         items (seq data-input)]
    (if items
      (recur
        (let [[kw db fieldname] (first items)]
          (assoc output kw (select db (fields fieldname) (where {:username suser})))) 
        (next items))
      output)))

Не тестировалось, так как у меня нет вашей базы данных, но, надеюсь, это даст вам представление о том, как это сделать без макросов или изменяемых глобальных переменных!

person mikera    schedule 30.09.2012
comment
Я создаю вариант этого предмета. Причина, по которой мне понадобились ключи для ввода данных, заключается в том, что мне нужно как вставлять, так и выбирать значения, и я также хочу маршрутизировать их по страницам, чтобы ключи при вводе данных были фактически именами страниц. Сейчас работаю над деструктуризацией. - person dizzystar; 01.10.2012
comment
Спасибо за параллелизм и глобальные проблемы. Первоначально у меня был этот материал на уровне страницы, но я переместил его, потому что хотел что-то попробовать, а затем оставил его там. Не уверен, есть ли у меня проблемы с параллелизмом прямо сейчас, так зачем использовать Clojure, верно? :D - person dizzystar; 01.10.2012

Хороший вопрос. Прежде всего, вот макрос, который вы просили:

(defmacro defquery [fname table fields ]
  (let [arg-name (symbol 'user-name)
        fname (symbol fname)]
    `(defn ~fname [~arg-name]
       (print ~arg-name (str ~@ fields)))))

Вы можете назвать это так:

(defquery suser-first-name db/firstNames [:firstname])

или если вы предпочитаете хранить все свои конфигурации на карте, тогда она примет строку в качестве первого аргумента вместо символа:

(defquery "suser-first-name" db/firstNames [:firstname])

Теперь, если вы не возражаете, что я рекомендую другое решение, я, вероятно, предпочел бы использовать единственную функцию, замкнутую вокруг конфигурации. Что-то такое:

(defn make-reader [query-configurations]
  (fn [query-type user-name]
    (let [{table :table field-names :fields} 
           (get query-configurations query-type)]
      (select table
             (apply fields field-names)
             (where {:username suser})))))

(def data-input {:firstname  {:table db/firstNames  :fields :firstname} 
                 :middlename {:table db/middleNames :fields :middlename}
                 :lastname   {:table db/lastNames   :fields :lastname}})

(def query-function (make-reader data-input))

;; Example of executing a query
(query-function :firstname "tom")

Кстати, есть еще один способ использования Кормы:

;; This creates a template select from the table
(def table-select (select* db/firstNames))

;; This creates new select query for a specific field
(def first-name-select (fields table-select :firstname))

;; Creating yet another query that filters results by :username
(defn mkselect-for-user [suser query] 
  (where query {:username suser}))

;; Running the query for username "tom"
;; I fully specified exec function name only to show where it comes from.
(korma.core/exec (mkselect-for-user "tom" first-name-select)) 

Для получения дополнительной информации я настоятельно рекомендую просмотреть исходники Korma.

person Ivan Koblik    schedule 30.09.2012
comment
Это хороший ответ. Я не хочу связывать все в Korma прямо сейчас, потому что я хочу иметь возможность вносить изменения как можно проще. Я создаю довольно интересный пользовательский интерфейс, и база данных может быть изменена. Нынешний дизайн — лучшее, что мне удалось придумать, чтобы сбалансировать гибкость и элегантность, хотя я уверен, что есть множество других вариантов и аргументов. Это самостоятельный проект, поэтому я должен подумать о человеке, который хотел бы его поддерживать. Мне не очень понравилась идея использовать здесь макрос, поэтому я рад, что вы оба направили меня в правильном направлении. - person dizzystar; 01.10.2012
comment
@dizzystar Конечно, только вы можете в конечном итоге решить, что лучше для вашего проекта. Кстати, вы не должны сильно беспокоиться о параллелизме, если используете макрос так же, как defn. Вместо того, чтобы создавать карту конфигураций, вы можете просто вызвать макрос несколько раз с разными аргументами (так же, как defn). Но общий консенсус заключается в том, чтобы никогда не использовать макрос там, где достаточно функции. - person Ivan Koblik; 01.10.2012