Каков * правильный * способ обработки POST в FP? - PullRequest
8 голосов
/ 13 января 2011

Я только начинаю работать с FP и использую Scala, что может быть не лучшим способом, так как я всегда могу вернуться к императивному стилю, если дела идут плохо. Я бы просто не хотел. У меня есть очень специфический вопрос, который указывает на более широкий пробел в моем понимании ФП.

Когда веб-приложение обрабатывает GET-запрос, пользователь запрашивает информацию, которая уже существует на веб-сайте. Приложение должно только обрабатывать и форматировать данные каким-либо образом. Путь FB ясен.

Когда веб-приложение обрабатывает запрос POST, пользователь хочет изменить информацию, хранящуюся на сайте. Правда, информация обычно не хранится в переменных приложения, она находится в базе данных или в простом файле, но, тем не менее, у меня возникает ощущение, что я не ворчу ФП правильно.

Существует ли шаблон для обработки обновлений статических данных на языке FP?

Моя смутная картина такова, что приложению передается запрос и текущее состояние сайта. Приложение делает свое дело и возвращает новое состояние сайта. Если текущее состояние сайта не изменилось с момента запуска приложения, новое состояние становится текущим, и ответ отправляется обратно в браузер (это мое тусклое изображение стиля Clojure); если текущее состояние было изменено (другим потоком, ну, что-то еще происходит ...

Ответы [ 4 ]

6 голосов
/ 13 января 2011

Одним из способов решения этой проблемы в чистой среде FP являются монады, такие как в Haskell (например, IO и State), но есть альтернативы, такие как «уникальные типы» (которые допускают только одну ссылку на значение) в Clean.

Здесь не так много grok : если у вас изменчивое состояние, вам нужно каким-то образом ограничить доступ к нему таким образом, чтобы каждое изменение этого состояния было воспринимается как «новая версия» этой структуры от остальной части программы.Например, вы можете думать о IO Хаскелла как о «остальном мире», но с какими-то часами.Если вы что-то делаете с IO, часы тикают, и вы никогда больше не увидите тот же IO.В следующий раз, когда вы прикоснетесь к нему, это будет другой ввод-вывод, другой мир, в котором все, что вы сделали, уже произошло.

В реальной жизни вы можете «видеть», как вещи «меняются» в фильме - это обязательный вид.Но если вы берете фильм, вы видите просто ряд маленьких неизменных картинок, без каких-либо следов «изменений» - это вид FP.Оба представления являются действительными и «истинными» в своем собственном контексте.

Однако, если вы используете Scala, вы можете иметь изменяемое состояние - здесь нет проблем.Scala просто не нуждается в какой-либо специальной обработке для этого, и нет ничего плохого в том, чтобы использовать его (хотя это считается «хорошим стилем», чтобы сохранить «нечистые» пятна как можно меньше).

3 голосов
/ 17 января 2011

Наиболее идиоматический ответ FP на асинхронные изменения состояния в сети: стиль передачи продолжения , я полагаю: текущей выполняющейся функции предоставляется функция продолжение как «следующая»action ", вызов которого с аргументами, полученными в результате текущего вычисления (аналог императива return ), представляет передачу состояния.

Применительно к сети это означает, что когда серверу требуется вводот пользователя, он просто сохраняет продолжение сеанса.Когда пользователь отвечает некоторой информацией, сохраненное продолжение восстанавливается, предоставленные им входные данные обрабатываются с помощью некоторых вычислений, а затем возвращаются в качестве значения продолжения.

Пример инфраструктуры веб-приложений на основе продолжений можно найти здесь .Подробное описание идеи на высоком уровне: здесь .См. Также множество ресурсов и приложений FP, реализующих этот здесь .

Продолжения поддерживаются в Scala с 2.8 , и существует 200-300Пример LoC для веб-сервера в стиле продолжения в дистрибутиве.

3 голосов
/ 13 января 2011

Ответ - монады. В частности, государственные и международные монады справятся с этим полностью.

Вот пример того, как это будет работать:

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 и питающей базу данных. Думайте о базе данных как о неизменном объекте - каждый раз, когда вы «обновляете» ее, вы получаете новый объект базы данных.

0 голосов
/ 13 января 2011

Примечание: я совсем не знаю 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)

Вместо традиционной структуры данных карта может быть просто функцией. Чтобы добавить или удалить записи с карты, функция составляется из существующей карты для создания новой карты.

Мне тоже нравится думать о таких веб-приложениях. Веб-приложение конвертирует запросы в транзакции. Транзакция изменяет одно состояние на другое, но может применяться к текущему состоянию, или к некоторому прошлому состоянию, или к некоторому неизвестному будущему состоянию. Транзакции сами по себе бесполезны; что-то должно быть их последовательность, применяя их один за другим. Но обработчик запросов вообще не должен думать об этом.

В качестве примера рассмотрим состояние моделей каркаса Happstack . Входящий запрос направляется обработчику, который работает внутри монады. Частично благодаря некоторой магии TH , фреймворк сериализует полученный mote и добавляет его в конец растущего журнала транзакций. Чтобы определить самое последнее состояние, можно просто пройти по файлу журнала, применяя транзакции в последовательности. (Happstack также может писать «контрольные точки», но они не являются строго необходимыми для работы.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...