代码之家  ›  专栏  ›  技术社区  ›  Rodrigo Oliveira

函数式编程中的共享资源

  •  2
  • Rodrigo Oliveira  · 技术社区  · 8 年前

    上下文

    我正在使用Clojure并遵循函数式编程范式编写一个应用程序。在这个应用程序中,我有两个HTTP端点: /rank /invite 在里面 /等级 该应用程序根据客户的分数对其进行排名。在里面 /邀请 应用程序会收到来自一个客户到另一个客户的邀请,这应该会导致一些客户的分数发生变化。

    问题

    来自客户的数据保存在一个名为 record .

    暂时撇开参照透明度不谈, 记录 应该是端点之间的共享资源,一个读取并在排名函数中使用它来响应HTTP请求,另一个读取它并更新其中的分数。

    现在,考虑到函数编程, 记录 无法更新,因此 /邀请 端点应该读取它并返回一个新的 record' ,问题是, /等级 端点设置为使用 记录 ,但是当一个新的 记录' 它应该使用它而不是原始的。

    我解决这个问题的想法

    我理解,在这种情况下,整个应用程序不能完全、功能上、纯粹。它从文件中读取初始输入并接收来自外部环境的请求,所有这些都使得处理这些部分的函数不具有引用透明性。几乎每个程序都会有这些非功能性代码的小部分,但为了尝试不向应用程序添加更多的非功能性功能,而这个应用程序只是为了练习一些功能性编程,我没有坚持 记录 因为如果是这样的话,问题就会得到解决,因为我只需要调用一个函数来更新数据库上的记录。

    到目前为止,我的最佳想法是:端点是用 routes Compojure的功能,因此在 /邀请 端点I应处理新的 记录' 向量,并重新创建 /等级 端点为其提供 记录' 。这部分重建 /等级 是我正在努力的,我想打电话给 路线 并再次定义所有端点,希望它将覆盖 路线 ,但正如我所料,我相信Compojure遵循函数编程,一旦创建,路由是不可变的,新的路由调用不会覆盖任何内容,它只会创建新的路由,这些路由将保留在空白中,而不会附加到HTTP请求。

    那么,用纯函数代码可以做我想做的事情吗?或者,打破参照透明度是不可避免的,我应该坚持下去 记录 更新文件或数据库?

    附言:我不知道这是否相关,但我对Clojure和任何类型的网络交互都是新手。

    1 回复  |  直到 8 年前
        1
  •  3
  •   Piotrek Bzdyl    8 年前

    Clojure(与Haskell相反)不是纯的,它有自己的结构来管理对共享状态的更改。它并没有使用类型系统(如Haskell中的IO monad)隔离不纯,而是提倡使用纯函数并使用不同类型的引用管理状态( atom , agent , ref )定义一个明确的语义,状态是如何以及何时改变的。

    对于您的场景Clojure的 原子 将是最简单的解决方案。它就如何管理其状态提供了明确的合同。

    我会创建一个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中的数据的当前版本和其他可选参数调用函数,函数的结果将作为atom的新值进行安装。