クロージュア:マップから関数を動的に作成する — マクロの時間ですか?
-
11-12-2019 - |
質問
次のように始まる関数があります。
(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 の値が不明であり、data-input 内のキーの量が不明であること。
-- そろそろマクロを作ろうかと思っているのですが、作ったことがないので迷っています。
コンテキストを少し説明すると、関数は分解される値を返さなければなりませんが、この部分が解決されれば、これらすべてを一般化することが可能になると思います。
(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))]]))
解決
いくつかの提案:
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)))
データベースをセットアップしていないためテストしていませんが、マクロや変更可能なグローバルを使用せずにこれを行う方法のアイデアが得られれば幸いです。
他のヒント
素敵な質問。まず最初にあなたが尋ねたマクロは次のように頼みました:
(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")
.
kormaを使用するもう1つの方法がある:
;; 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ソースを見ることを強くお勧めします。
所属していません StackOverflow