Clojure:从映射中动态创建函数——宏的时间到了?
我有一个函数是这样开始的:Clojure:从映射中动态创建函数——宏的时间到了?,clojure,macros,refactoring,noir,hiccup,Clojure,Macros,Refactoring,Noir,Hiccup,我有一个函数是这样开始的: (defn data-one [suser] (def suser-first-name (select db/firstNames (fields :firstname) (where {:username suser}))) (def suser-middle-name (select db/middleNames (fields :middlen
(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函数,即:1的值未知,且数据输入中的键数量未知
--我认为现在是创建宏的时候了,但我以前从未创建过宏,所以我对这个想法犹豫不决
给出一点上下文,函数必须返回要解构的值,但我认为,一旦我解决了这一问题,将所有这些概括起来是可行的:
(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))]]))
问得好。首先,这是您要求的宏:
(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])
(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")
或者,如果希望将所有配置保留在映射中,则它将接受字符串作为第一个参数,而不是符号:
(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的方法:
;; 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))
欲了解更多信息,我强烈建议查看。一些建议:
- 函数内部的
非常糟糕-您正在改变全局环境,它可能会导致各种并发问题。我建议将结果存储在地图中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)))
没有测试,因为我没有您的数据库设置-但希望这能让您了解如何在没有宏或可变全局变量的情况下实现这一点 我正在创建此项目的变体。我之所以需要在数据输入中使用键,是因为我必须插入和选择值,而且我还想在页面中传递它们,以便数据输入中的键实际上就是页面名称。正在进行解构。同时感谢您对并发性和全局性的关注。我本来有这个东西的页面级别,但我把它移了出来,因为我想尝试一些东西,然后我就把它放在那里了。不确定我现在是否有并发问题,那么为什么要使用Clojure,对吗这是一个很好的答案。我现在不想把Korma的一切都捆绑起来,因为我希望能够尽可能轻松地进行更改。这是我正在创建的一个相当有趣的UI,数据库可能会发生变化。当前的设计是我在平衡灵活性和优雅性时所能想到的最好的设计,尽管我确信还有很多其他的选择和理由。这是一个自我项目,所以我必须考虑想要维护它的人。我对在这里使用宏的想法不太满意,所以我很高兴你们两个都给我指明了正确的方向。@dizzystar当然只有你们才能最终决定什么对你们的项目最有利。顺便说一句,如果您使用宏的方式与使用
defn
的方式相同,那么您不应该非常关心并发性。您可以使用不同的参数多次调用宏,而不是创建配置映射(与defn
的方法相同)。但普遍的共识是永远不要在功能足够的地方使用宏。