【问题标题】:How can I reduce the duplication in the Clojure code below?如何减少下面 Clojure 代码中的重复?
【发布时间】:2017-01-11 22:00:58
【问题描述】:

我有以下带有 render 函数的 Clojure 代码,该函数使用 enlive-html 呈现 html 页面。根据所选语言使用不同的 html 模板。

如您所见,有很多代码重复,我想将其删除。

我正在考虑编写一些宏,但如果我理解正确,该语言(即 lang 参数)在宏执行时不可用,因为它是在请求中提供的,并且是在执行时而不是在编译时提供的。

我还尝试修改 enlive 以便稍后添加 i18n 支持,但我的 Clojure 技能还没有。

所以问题是:

如何去除下面代码中的重复代码?

enlive-html 是要走的路还是我应该使用另一个库? 有没有支持 i18n 的类似 enlive 的库?

谢谢!

在此处查看代码:

(ns myapp.core
  (:require [net.cgrand.enlive-html :as e))

(deftemplate not-found-en "en/404.html"
  [{path :path}]
  [:#path] (e/content path))

(deftemplate not-found-fr "fr/404.html"
  [{path :path}]
  [:#path] (e/content path))


(defn getTemplate [page lang]
  (case lang
      :en (case page
                :page/not-found not-found-en)
      :fr (case page
                :page/not-found not-found-fr)))

(defn render [lang [page params]]
  (apply (getTemplate page lang) params))

【问题讨论】:

  • 你到底是什么意思,回复:“语言在宏执行时不可用”?
  • ...如果您的宏正在生成您的deftemplates,您只需在编译时运行它们即可执行该操作。
  • 这属于codereview.stackexchange.com,因为它已经工作了。
  • @CharlesDuffy 用词不好。我在正文中添加了解释
  • @vidi, ...正如我在之前的评论中所说,您不需要知道编译时任何给定请求将使用哪个lang;您只需要知道哪些langs 可以 可以使用——您可以使用哪些翻译。在编译时生成所有可能的扩展,你就完成了,不需要任何运行时数据。

标签: clojure enlive


【解决方案1】:

一方面,编写一个宏来为任意一组语言生成您在此处所拥有的确切代码并不难。另一方面,可能有比使用 deftemplate 更好的方法 - defd 是您希望在源代码中按名称引用的东西,而您只希望自动创建和使用这个东西。但是我不熟悉 enlive API,所以我不能说你应该做什么。

如果您决定改用宏,您可以编写如下内容:

(defmacro def-language-404s [languages]
  `(do
     ~@(for [lang languages]
         `(deftemplate ~(symbol (str "not-found-" lang)) ~(str lang "/404.html")
            [{path# :path}]
            [:#path] (e/content path#)))
     (defn get-template [page# lang#]
       (case page#
         :page/not-found (case lang#
                           ~@(for [lang languages
                                   clause [(keyword lang)
                                           (symbol (str "not-found-" lang))]]
                               clause))))))

user> (macroexpand-1 '(def-language-404s [en fr]))
(do
  (deftemplate not-found-en "en/404.html"
    [{path__2275__auto__ :path}]
    [:#path] (content path__2275__auto__))
  (deftemplate not-found-fr "fr/404.html"
    [{path__2275__auto__ :path}]
    [:#path] (content path__2275__auto__))
  (defn get-template [page__2276__auto__ lang__2277__auto__]
    (case page__2276__auto__
      :page/not-found (case lang__2277__auto__
                        :en not-found-en
                        :fr not-found-fr))))

【讨论】:

  • 感谢您的回答。宏会很好,至少现在是这样,但你答案中的那个对我不起作用。它抱怨建筑 [:#path] 见这里:gist.github.com/anonymous/ec0f2b18269f28d5d1c333945cf386ac
  • 嗯? :#path 是从您的问题中逐字复制的;我的宏扩展为您需要的代码。我不知道您的 repl 错误是怎么回事,但它们与:#path 无关,实际上他们甚至都没有提及。你复制/粘贴错了吗?引导repl搞砸了吗?谁知道,但错误信息与代码不匹配。
  • 我没有复制粘贴错误。 boot 和 lein 都使用相同版本的 nrepl,所以我认为它与 boot 无关。我尝试了 Clojure v1.7.0 和 1.8.0,它的行为相同。我在上面说过,问题与 :#path 中的字符 # 有关,因为如果我删除 # 错误就不再存在。我猜是因为 # 在宏内部有一个含义,所以它应该被转义或其他东西,但我还没有找到如何做到这一点。请参阅此要点gist.github.com/anonymous/312877d8e15a0f5791ff9bab759a203f
  • 我可以确认它在 lein repl 中也不起作用。我不知道 boot/lein repl 有什么问题(尽管如果他们都有,我认为这是一个 nrepl 问题)。如果您直接java -jar clojure.jar,或者如果您将其写入 .clj 文件并需要该文件,则 sn-p 确实 可以正常工作,这表明该宏就 Clojure 而言是正确的;是你的工具搞砸了。
  • 进一步研究表明这是 sjacket 中的一个错误,lein repl 和 boot repl 用于解析多行表单的代码。另一种查看方式是注意,如果您将我的代码粘贴为一行,它可以正常工作。 sjacket 已经三年没有更新了,所以我不知道有人会如何在 lein/boot 中解决这个问题。
【解决方案2】:

在使用了相当多的 Macro-Fu 之后,我得到了一个令我满意的结果。在一些不错的 stackoverflowers 的帮助下,我在 enlive 之上编写了以下宏:

(ns hello-enlive
  (:require [net.cgrand.enlive-html :refer [deftemplate]]))

(defn- template-name [lang page] (symbol (str "-template-" (name page) "-" (name lang) "__")))
(defn- html-file [lang page] (str (name lang) "/" (name page) ".html"))
(defn- page-fun-name [page] (symbol (str "-page" (name page))))

(defmacro def-page [app languages [page & forms]]
  `(do
     ~@(for [lang languages]
         `(deftemplate ~(template-name lang page) ~(html-file lang page)
            ~@forms))

      (defn ~(page-fun-name page) [lang#]
         (case lang#
           ~@(for [lang languages
                   clause [(keyword lang) (template-name lang page)]]
               clause)))

      (def ^:dynamic ~app
        (assoc ~app ~page ~(page-fun-name page)))
      ))

(defmacro def-app [app-name languages pages]
  (let [app (gensym "app__")]
    `(do
       (def ~(vary-meta app merge {:dynamic true}) {})

       ~@(for [page# pages]
           `(def-page ~app ~languages ~page#))

       (defn ~app-name [lang# [page# params#]]
         (apply (apply (get ~app page#) [lang#]) params#)))))

...然后像这样使用:

html 模板存储在这样的树中

html/fr/not-found.html
html/fr/index.html
html/en/not-found.html
html/en/index.html
...

...渲染逻辑如下:

(def-app my-app [:en :it :fr :de]
  [ [:page/index [] ]

    ;... put your rendering here

    [:page/not-found [{path :path}]
      [:#path] (content path)]])

...用法如下:

...
(render lang [:page/index {}])
(render lang [:page/not-found {:path path}])
...

结果,虽然它可能可以改进,但我认为非常好,没有重复和样板代码。

【讨论】:

    猜你喜欢
    • 2021-08-12
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多