在 FP 中处理 POST 的“正确”方法是什么?

发布于 2024-10-12 08:38:00 字数 476 浏览 6 评论 0原文

我刚刚开始使用 FP,并且正在使用 Scala,这可能不是最好的方法,因为如果事情变得困难,我总是可以退回到命令式风格。我只是不想。我有一个非常具体的问题,它指出了我对 FP 的理解中存在的更广泛的缺陷。

当 Web 应用程序处理 GET 请求时,用户需要网站上已存在的信息。应用程序只需以某种方式处理和格式化数据。 FB的方式很明确。

当 Web 应用程序处理 POST 请求时,用户希望更改网站上保存的信息。确实,这些信息通常不会保存在应用程序变量中,而是保存在数据库或平面文件中,但我仍然感觉自己没有正确理解 FP。

是否有一种模式可以处理 FP 语言中静态数据的更新?

我对此的模糊描述是应用程序收到请求和当时的站点状态。应用程序执行其操作并返回新的站点状态。如果自应用程序启动以来当前站点状态没有更改,则新状态将成为当前状态,并且回复将发送回浏览器(这是我对 Clojure 风格的模糊图像);如果当前状态已更改(被另一个线程更改,那么,会发生其他情况......

I'm just getting started with FP and I'm using Scala, which may not be the best way, since I can always fall back to an imperative style if the going gets tough. I'd just rather not. I've got a very specific question that points to a broader lacuna in my understanding of FP.

When a web application is processing a GET request, the user wants information that already exists on the web-site. The application only has to process and format the data in some way. The FB way is clear.

When a web application is processing a POST request, the user wants change the information held on the site. True, the information is not typically held in application variables, it's in a database or a flat-file, but still, I get the feeling I'm not grokking FP properly.

Is there a pattern for handling updates to static data in an FP language?

My vague picture of this is that the application is handed the request and the then-current site state. The application does its thing and returns the new site-state. If the current site-state hasn't changed since the application started, the new state becomes the current state and the reply is sent back to the browser (this is my dim image of Clojure's style); if the current state has been changed (by another thread, well, something else happens ...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

淤浪 2024-10-19 08:38:00

在纯 FP 环境中处理此类问题的一种方法是像 Haskell 中的 monad(例如 IO 和 State),但在 Clean 中还有其他选择,例如“唯一类型”(只允许对一个值进行一次引用)。

这里没有太多需要理解的地方:如果您有可变状态,那么您需要以某种方式限制对其的访问,以便该状态的每次更改都被感知为程序其余部分的该结构的“新版本”。例如,您可以将 Haskell 的 IO 视为“世界的其他部分”,但附加了一种时钟。如果你用 IO 做一些事情,时钟就会滴答作响,你再也不会看到相同的 IO。下次你触摸它时,这是另一个 IO,另一个世界,你所做的一切都已经发生了。

在现实生活中,你可以在电影中“看到”事物如何“变化”——这是必然的观点。但如果你抓起胶片,你会看到一串不变的小图片,没有任何“变化”的痕迹——这就是 FP 视图。这两种观点在各自的背景下都是有效且“真实”的。

但是,如果您使用 Scala,您可以拥有可变状态 - 这里没有问题。 Scala 根本不需要对此进行任何特殊处理,并且使用它没有任何问题(尽管它被认为是“良好的风格”,可以使“不纯”点尽可能小)。

One way to deal with this kind of problem in a pure FP environment are monads like in Haskell (e.g. IO and State), but there are alternatives like "unique types" (which allow only one reference to a value) in Clean.

There is not much to grok here: If you have mutable state, then you need somehow to restrict access to it, in a way that every change of that state is perceived as a "new version" of that structure from the rest of the program. E.g. you can think of Haskell's IO as "rest of the world", but with a kind of clock attached to it. If you do something with IO, the clock ticks, and you never see the same IO again. The next time you touch it it's another IO, another world where everything you did has already happened.

In real life you can "see" how things "change" in a movie - that's the imperative view. But if you grab the film, you see just a string of small immutable pictures, without any trace of a "change" - that's the FP view. Both views are valid and "true" in their own context.

However, if you use Scala, you can have mutable state - no problem here. Scala simply don't need any special handling for this, and there is nothing wrong in using it (although it's considered "good styl"e to keep the "impure" spots as small as possible).

乱了心跳 2024-10-19 08:38:00

答案是单子。具体来说,状态和 io monad 将完全处理这个问题。

这是一个如何工作的示例:

trait Monad[A] {
  def flatMap[B, T[X] <: Monad[X]](f: A => T[B]): T[B]
}

class State[A](st: A) extends Monad[A] {
  def flatMap[B, T[X] <: Monad[X]](f: A => T[B]): T[B] = f(st)
  def map[B](f: A => B): State[B] = new State(f(st))
}

object IO extends Monad[String] {
  def getField = scala.util.Random.nextString(5)
  def getValue = scala.util.Random.nextString(5)
  def fieldAndValue = getField + "," + getValue
  def flatMap[B, T[X] <: Monad[X]](f: String => T[B]): T[B] = f(fieldAndValue)
}

object WebServer extends Application {
    def programLoop(state: State[Map[String, String]]): State[Map[String, String]] = programLoop(
        for {
          httpRequest <- IO
          database <- state
        } yield database.updated(httpRequest split ',' apply 0, httpRequest split ',' apply 1)
    )

    programLoop(new State(Map.empty))
}

请注意,程序中没有任何东西是可变的,但是,它会不断更改“数据库”(由不可变的 Map 表示) )直到内存耗尽。这里的 IO 对象通过提供随机生成的键和值对来模拟假设的 HTTP PUT 请求。

因此,这是处理 HTTP PUT 并提供数据库的功能程序的一般结构。将数据库视为不可变的对象 - 每次“更新”它时,您都会获得一个新的数据库对象。

The answer is monads. Specifically, the state and io monads would handle this completely.

Here's an example of how this would work:

trait Monad[A] {
  def flatMap[B, T[X] <: Monad[X]](f: A => T[B]): T[B]
}

class State[A](st: A) extends Monad[A] {
  def flatMap[B, T[X] <: Monad[X]](f: A => T[B]): T[B] = f(st)
  def map[B](f: A => B): State[B] = new State(f(st))
}

object IO extends Monad[String] {
  def getField = scala.util.Random.nextString(5)
  def getValue = scala.util.Random.nextString(5)
  def fieldAndValue = getField + "," + getValue
  def flatMap[B, T[X] <: Monad[X]](f: String => T[B]): T[B] = f(fieldAndValue)
}

object WebServer extends Application {
    def programLoop(state: State[Map[String, String]]): State[Map[String, String]] = programLoop(
        for {
          httpRequest <- IO
          database <- state
        } yield database.updated(httpRequest split ',' apply 0, httpRequest split ',' apply 1)
    )

    programLoop(new State(Map.empty))
}

Note that there isn't a single thing that is mutable in the program, and, however, it will keep changing the "database" (represented by an immutable Map) until it runs out of memory. The IO object here is simulating hypothetical HTTP PUT requests by feeding pairs of randomly generated key and values.

So, this is the general structure of a functional program processing HTTP PUT and feeding a database. Think of a database as an immutable object -- each time you "update" it, you get a new database object.

提笔落墨 2024-10-19 08:38:00

对于通过网络异步修改状态的 FP 最惯用的答案是延续传递风格,我猜:当前运行的函数被提供了一个延续函数作为“下一步操作”,它的调用使用当前计算产生的参数(类似于命令式返回)代表状态传递。

应用于网络,这意味着当服务器需要用户输入时,它只保存每个会话的延续。当用户响应一些信息时,保存的延续被恢复,他提供的输入经过一些计算处理,然后作为延续的值返回。

您可以在此处找到基于延续的 Web 应用程序框架示例。该想法的详细高级文章位于此处。另请参阅此处实现此功能的大量资源和 FP 应用程序。

从 2.8 开始,Scala 支持延续,并且有一个 200-300 LoC Web 服务器示例 在分布中采用连续传递风格。

The most idiomatic FP answer to asynchronous modifications of state over the web is continuation-passing style, I guess: the current running function is provided a continuation function as a "next action", whose calling with arguments resulting from the current computation (the analogous of the imperative return) represents state-passing.

Applied to the web, it means that when the server needs input from the user, it just saves the per-session continuation. When the user responds with some information, the saved continuation is restored, the input he provided is processed by some computation, and it is then returned as the value of the continuation.

You can find an example of continuations-based web application framework here. A detailed high-level writeup of the idea is here. See also the plethora of ressources and FP applications implementing this here.

Continutations are supported in Scala as of 2.8, and there is a 200-300 LoC example of a webserver in continuation-passing style in the distribution.

幸福还没到 2024-10-19 08:38:00

注意:我根本不懂 Scala,所以我只是猜测示例中的语法。

这是实现映射的一种功能方法:

val empty           = x =>                scala.None
def insert(k, v, m) = x => if k == x then scala.Some(v) else m(k)
def delete(k, m)    = x => if k == x then scala.None    else m(k)
def get(k, m)       = m(k)

映射可以不是传统的数据结构,而是可以是只是一个函数。要从映射中添加或删除条目,需要将函数与现有映射组合起来以生成新映射。

我也喜欢这样思考网络应用程序。 Web 应用程序将请求转换为事务。事务将一种状态转变为另一种状态,但它可以应用于当前状态,或某些过去的状态,或某些未知的未来状态。仅仅交易是没有用的;必须有某种东西对它们进行排序,一个接一个地应用它们。但请求处理程序根本不必考虑这一点。

作为示例,请看一下 Happstack 框架模型的状态。传入请求被路由到在 monad 内运行的处理程序。部分归功于一些 TH 魔法,框架序列化了生成的 mote 并将其添加到不断增长的事务日志的尾部。要确定最新状态,只需单步浏览日志文件,按顺序应用事务即可。 (Happstack 也可以编写“检查点”,但它们并不是操作所必需的。)

Note: I don't know Scala at all, so I'm just guessing at the syntax from examples.

Here's one functional way of implementing a map:

val empty           = x =>                scala.None
def insert(k, v, m) = x => if k == x then scala.Some(v) else m(k)
def delete(k, m)    = x => if k == x then scala.None    else m(k)
def get(k, m)       = m(k)

Instead of a traditional data structure, a map can be just a function. To add or remove entries from the map, a function is composed with the existing map to yield a new map.

I like thinking of webapps like this too. A webapp converts requests into transactions. A transaction mutates one state to another, but it could be applied to the current state, or some past state, or some unknown future state. Transactions alone aren't useful; something has to be sequencing them, applying them one after another. But the request handler doesn't have to think about that at all.

As an example, take a look at how the Happstack framework models state. An incoming request gets routed to a handler which runs inside a monad. Thanks in part to some TH magic, the framework serializes the resulting mote and adds it to the tail of the growing transaction log. To determine the most recent state, one can simply step through the log file, applying transactions in sequence. (Happstack can write "checkpoints" too, but they're not strictly necessary for operation.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文