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

时间:2011-01-13 02:30:21

标签: scala post functional-programming web-frameworks

我刚刚开始使用FP而我正在使用Scala,这可能不是最好的方法,因为如果事情变得艰难,我总能回归到命令式的风格。我只是不愿意。我有一个非常具体的问题,指出我对FP的理解存在更广泛的空白。

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

当Web应用程序正在处理POST请求时,用户希望更改站点上保存的信息。没错,信息通常不会保存在应用程序变量中,而是存储在数据库或平面文件中,但我仍然觉得我没有正确地 grokking FP。

是否存在处理FP语言中静态数据更新的模式?

我对此的模糊描述是应用程序处理请求和当时的站点状态。应用程序执行其操作并返回新的站点状态。如果自应用程序启动后当前站点状态没有改变,则新状态变为当前状态,并且回复被发送回浏览器(这是我对Clojure风格的暗淡图像);如果当前状态已经改变(通过另一个线程,那么,其他事情发生了......

4 个答案:

答案 0 :(得分:6)

在纯FP环境中处理这类问题的一种方法是像Haskell中的monad(例如IO和State),但是在Clean中有一些替代方法,比如“unique type”(只允许对一个值进行一次引用)

这里没有太多的 grok :如果你有可变的状态,那么你需要以某种方式限制对它的访问,其方式是该状态的每次变化都是感知的作为该结构的其他部分的“新版本”。例如。您可以将Haskell的IO视为“世界其他地方”,但附带一种时钟。如果您使用IO执行某些操作,则时钟会打勾,并且您再也看不到相同的IO。下次你触摸它时,它是另一个IO,另一个世界,你所做的一切都已经发生了。

在现实生活中,你可以“看到”电影中的“变化” - 这是必要的观点。但是如果你抓住这部电影,你会看到一串不可变的小图片,没有任何“改变”的痕迹 - 这就是FP视图。这两种观点在他们自己的背景下都是有效的和“真实的”。

但是,如果您使用Scala,可以具有可变状态 - 此处没有问题。 Scala根本不需要任何特殊处理,使用它没有任何问题(虽然它被认为是“好的风格”,以保持“不纯”的点尽可能小)。

答案 1 :(得分:3)

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

以下是一个如何运作的示例:

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和提供数据库的功能程序的一般结构。将数据库视为不可变对象 - 每次“更新”它时,都会得到 new 数据库对象。

答案 2 :(得分:3)

对于通过网络异步修改状态的最惯用FP答案是continuation-passing style,我想:当前运行的函数提供了一个 continuation 函数作为“下一个动作”,其使用当前计算产生的参数调用(类似于命令式 return )表示状态传递。

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

您可以找到基于continuation的Web应用程序框架here的示例。该想法的详细高级写法是here。另请参阅实现此here的过多资源和FP应用程序。

Continutations为supported in Scala as of 2.8,并且在分发中有延续传递样式200-300 LoC example of a webserver

答案 3 :(得分:-1)

注意:我根本不了解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)

地图可以只是一种功能,而不是传统的数据结构。要在地图中添加或删除条目,可以使用现有地图组成一个函数以生成新地图。

我也喜欢考虑这样的webapps。 Web应用程序将请求转换为事务。事务将一个状态变为另一个状态,但它可以应用于当前状态,或某个过去状态,或某个未知状态。仅交易无用;必须先对它们进行排序,然后逐一应用它们。但是请求处理程序根本不需要考虑它。

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