我刚开始在node-js端使用clojurescript。我正在使用它来构建将在节点上运行的命令行工具。 现在我已经设置了我的概念证明并且或多或少地做了我想要的时间来更好地组织代码。
在JS上,当我需要类似于已配置的http客户端的东西时,我通常会导出一个接受基本参数的函数,并返回一个带有绑定到该参数的方法的对象(通常使用显示模块模式)。类似于在OOP上创建新实例的东西。 以下是我将如何在JS上执行此操作的一个小示例:
const request = require('request')
module.exports = (user, pass, baseUrl) => {
const client = request.defaults({baseUrl, auth: {user, pass}})
const getSomething = (name) => client.get('resources/' + name)
const createSomething = (name, options) => client.post('resources', {...})
return { getSomething, createSomething }
}
然而在clojurescript上我找不到合适的方法。所有定义都是在编译时计算的顶级声明,并且构造如上所述的结构将需要使用客户端参数声明我的所有函数,然后部分应用它们并且它们在逻辑体上使用它们。这可能是这样的:
(ns some-client [:require ["request" :as request]])
(defn get-something [client, name]
(.get client (str "resources/" name)))
(defn create-something [client, name, options]
(.post client (str "resources") {:name name :data: options}))
(defn make-client [usr, pass, baseUrl]
(let [client (.defaults request {:auth {:user usr :pass pass} :baseUrl baseUrl})]
{:get-something (partial get-something client)
:create-something (partial create-something client)}))
这可能看起来不那么糟糕,但只要你需要在另一个地方使用它,所有功能都需要这样的客户端事情开始变得混乱。您需要在所有函数上接受客户端,如果其他命名空间只是您需要在其他地方使用的函数集合,您将被迫遵循相同的模式返回客户端创建者,接受客户端你依赖并确保将它传递给可能需要它的每个功能。我可以变得像这样可怕:
(ns other-helper)
(defn trivial-stuff [client name bla]
(let [get-something (client :get-something)]
(get-something name))) ; make things like filtering and that
(defn more-trivial-stuff [client name bla]
(let [get-something (client :get-something)])
(get-something name)) ; make things like filtering and that
(defn non-trivial-stuff [client name bla]
(->>
(trivial-stuff client name bla)
(more-trivial-stuff client name)))
(defn more-non-trivial-stuff [client name bla]
(->>
(trivial-stuff client name bla)
(more-trivial-stuff client name)))
(defn compile-utils [client]
{:more-non-trivial (partial more-non-trivial-stuff client)
:non-trivial (partial non-trivial-stuff client)})
我不能为客户端制作任何def
,因为我需要在运行时使用凭据,所以我必须接受所有这些东西作为参数并绑定结果,对我来说,看起来像很多样板和根本无法维护的重复代码。
clojurians有更好的方法吗?有关这方面的风格指南吗? 这是我第二次接近clojurescript,它起初看起来非常吸引人,但是一旦你开始构建非常琐碎的东西,它就会开始变得凌乱。
注意:为简单起见我没有管理任何js互操作或使用通道进行异步处理。我只是将js对象声明为普通的cljs贴图,并将所有内容都视为同步,但包括js interop,所有这些都会让事情变得更糟。
编辑(澄清):
我的问题不是关于这是否可能在clojure上,我知道这是可能的,因为CLJS和JS共享所需的一组功能以使其成为可能。然而,在完全不同的语言上使用相同的模式不仅感觉不对,而且由于lisp语法也看起来很丑陋。我能想到的其他选择也看起来很难看,并且涉及很多重复,因为它需要让客户端使用它并在每个函数上传递它,这会导致非常重复和分散注意力的代码。
要明确我将如何在js上使用它,就像这样
const makeClient = require('./my-http')
const client = makeClient('my-user','my-pass','http://google.es')
client.getSomething('dude')
正如你所看到的,我可以根据需要创建尽可能多的不同设置的客户端,我甚至可以进行一些解构,只选择我需要的方法,因为它们根本不依赖于它们的绑定。
答案 0 :(得分:1)
注意:我还没有使用Clojure / Script“愤怒”,所以这对我来说也是一次学习经历:)不幸的是,我还没有验证REPL中的代码。
如果我理解正确,JS模块模式是一个返回两个函数的字典的函数。在您的代码中的某个时刻,您“创建”此模块,也许给它一个名称,然后在代码中传递它,如下所示:
let client = require("mymodule")("user", "pass", "http://example.com");
client.getSomething("foo")
你可以用ClojureScript做同样的事情:
(defn create-client [user pass base]
(let [get-something (fn [name] ...)
create-something (fn [name other] ...)]
{:get get-something :create create-something}))
(let [client (create-client "user" "pass" "http://example.com")]
((client :get) "foo"))
现在可以说这可能看起来更笨重但是代码完全相同:关闭几个变量,在地图中粘贴两个函数,从地图中获取一个函数并调用它。
你的问题的第二部分看起来像关于全球状态 - 你必须随身携带client
对象,它感觉笨重。我不认为它在Javascript中看起来更好吗?
let client = require("mymodule")("user", "pass", "http://example.com");
let trivialStuff = (client, name, blah) => { client.getSomething(name); ... };
let moreTrivialStuff = (client, name, blah) => { client.getSomething(name); ... };
let nonTrivialStuff = (client, name, blah) => {
let result = trivialStuff(client, name, blah)
return moreTrivialStuff(client, name, result)
}
即。你还在经过client
。在初始化之后,你可以使它成为模块级变量,但是你失去了在运行时创建两个不同客户端的能力。
你是说用揭示模块模式你也会公开nonTrivialStuff
,所以你可以做client.nonTrivialStuff()
吗?
如何创建一个包含所有期望client
的函数的命名空间(可能只是一个包含JS requests
客户端的普通映射),并直接使用它们?
e.g。
(ns some-client [:require ["request" :as request]])
(defn make-client [usr pass base-url]
{:client (.defaults request {:auth {:user usr :pass pass} :baseUrl baseUrl})}) ;; you might want to use #js here, since you usually cannot pass CLJS maps to JS directly
(defn get-something [client name]
(.get (client :client) (str "resources/" name)))
(defn create-something [client name options]
(.post (client :client) (str "resources") {:name name :data options}))
然后在其他名称空间中:
(ns other-code [:require [some-client :as client]])
(def c (atom nil))
;; get user and pass from runtime, then call
(reset! c (client/make-client user pass base-url))
;; use like so
(client/get-something @c "name")
我选择将JS客户端对象放入CLJS映射中以实现灵活性 - 将来您可能希望在此映射中添加更多数据。当然,客户端代码不会改变,因为它应该将其视为不透明的值。