Clojure: crea dinamicamente funzioni da una mappa - tempo per una macro?
-
11-12-2019 - |
Domanda
Ho una funzione che inizia così:
(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...
)
.
Certo, non mi piace affatto.Ho questo motivo ripetendo in molte aree nella mia base di codice e mi piacerebbe generalizzare questo.
Allora, sono venuto con il seguente per iniziare:
(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}))))
.
Ci sono davvero alcune domande qui:
- Come posso decostruire l'ingresso dati in modo che crea funzioni x quando x è sconosciuta, cioè.che i valori di: uno è sconosciuto e che i quantitativi di chiavi nell'ingresso dati non sono sconosciuti.
- Sto pensando che questo sia un momento per creare una macro, ma non ne ho mai costruito uno prima, quindi sono esitante sull'idea.
E per dare un piccolo contesto, le funzioni devono restituire i valori per essere decostruiti, ma penso che una volta ottenuto questo pezzo risolto, generalizzando tutto questo sarà fattibile:
(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))]]))
. Soluzione
Alcuni suggerimenti:
- .
-
def
all'interno di una funzione è realmente brutto - stai alterando l'ambiente globale e può causare tutti i tipi di problemi con la concorrenza.Suggerirei di memorizzare i risultati in una mappa invece. - you non hai bisogno di una macro qui - Tutti i dati dei dati possono essere eseguiti relativamente facilmente all'interno di una funzione
Suggerisco quindi qualcosa come:
.(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)))
Non testato come non ho il tuo set-up del database - ma si spera che ti dà un'idea di come farlo senza macro o globali mutabili!
Altri suggerimenti
Nice domanda.Prima di tutto ecco la macro che hai chiesto:
(defmacro defquery [fname table fields ]
(let [arg-name (symbol 'user-name)
fname (symbol fname)]
`(defn ~fname [~arg-name]
(print ~arg-name (str ~@ fields)))))
.
Puoi chiamarlo in quel modo:
(defquery suser-first-name db/firstNames [:firstname])
.
O se preferisci mantenere tutte le configurazioni in una mappa, accetterà la stringa come primo argomento anziché un simbolo:
(defquery "suser-first-name" db/firstNames [:firstname])
.
.
Ora, se non ti dispiace che mi raccomanda un'altra soluzione, probabilmente avrei scelto di utilizzare una singola funzione chiusa attorno alla configurazione.Qualcosa del genere:
(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")
.
A proposito c'è un altro modo per usare Korma:
;; 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))
.
Per ulteriori informazioni consiglio vivamente di guardare a fonti Korma.