我正在使用Clojure编写应用程序并遵循函数式编程范例。在这个应用程序中,我有两个HTTP端点:/rank
和/invite
。在/rank
中,该应用根据其分数对客户列表进行排名。在/invite
中,应用程序会收到一个客户到另一个客户的邀请,这会导致一些客户的分数发生变化。
来自客户的数据保存在一个名为record
的地图矢量中。
暂时不考虑引用透明度,record
应该是端点之间的共享资源,一个读取它并在排名函数中使用它来响应HTTP请求而另一个读取它并更新其中的分数
现在,考虑到函数式编程,record
无法更新,因此/invite
端点应该读取它并返回一个新的record'
,问题是,/rank
端点设置为使用record
,但是当生成新的record'
时,它应该使用它而不是原始的。{/ p>
我理解在这种情况下,整个应用程序不能完全,功能上讲,纯粹。它从文件读取初始输入并从外部环境接收请求,所有这些都使得处理这些部分的函数不是引用透明的。而且几乎每个程序都会有这些非功能性代码的一小部分,但是为了尝试不向应用程序添加更多这些非功能性函数而这个应用程序只是为了进行一些函数式编程,我不会坚持{ {1}}数据库或其他东西,因为如果是这种情况,问题就会解决,因为我可以调用一个函数来更新数据库上的记录。
到目前为止,我最好的想法是:使用Compojure中的record
函数创建端点,因此在routes
端点中我应该处理新的/invite
向量,并重新创建{ {1}}端点为其提供record'
。重新构建/rank
的这部分是我正在努力的部分,我试图再次调用record'
并再次定义所有端点,希望它将覆盖/rank
的原始调用,但正如预期的那样,我相信Compojure遵循函数式编程,一旦创建,路由是不可变的,并且新的路由调用不会覆盖任何东西,它只会创建将留在虚空中的新路由,而不是附加到HTTP请求。
那么,用纯功能代码可以做我想要的吗?或者打破参照透明度是不可避免的,我应该将routes
保留到文件或数据库来更新它?
PS:我不知道这是否相关,但我是Clojure的新手和任何类型的网络互动。
答案 0 :(得分:3)
Clojure(与Haskell相反)并不纯粹,并且有自己的构造来管理对共享状态的更改。它不会使用类型系统(如Haskell中的IO monad)来隔离不纯,而是使用纯函数和使用不同类型的引用管理状态(atom
,agent
,ref
)定义明确的语义如何以及何时更改状态。
对于您的场景,Clojure的atom
将是最简单的解决方案。它就如何管理国家提供了明确的合同。
我会在原子中创建一个保存记录的var:
(def record (atom [])) ;; initial record is empty
然后在rank
端点中,您可以使用deref
或@
作为语法糖来使用其值:
(GET "/rank" []
(calculate-rank @record))
您的invite
端点可以使用swap!
以原子方式更新您的记录值:
(POST "/invite/:id" [id]
(invite id)
(swap! record calculate-new-rank id)
(create-response))
您的calculate-new-rank
功能看起来像这样:
(defn calculate-new-rank [current-record id]
;; do some calculations
;; create a new record value and return it
(let [new-record ...]
new-record))
您的函数将使用存储在atom和其他可选参数中的当前版本的数据进行调用,函数的结果将作为原子的新值安装。