2011-01-13 8 views
8

Ich fange gerade mit FP an und ich benutze Scala, was vielleicht nicht der beste Weg ist, da ich immer auf einen imperativen Stil zurückgreifen kann, wenn es schwierig wird. Ich würde es einfach nicht tun. Ich habe eine sehr spezifische Frage, die auf eine größere Lücke in meinem Verständnis von FP hinweist.Was ist der * richtige * Weg, um einen POST in FP zu behandeln?

Wenn eine Webanwendung eine GET-Anforderung verarbeitet, möchte der Benutzer Informationen, die bereits auf der Website vorhanden sind. Die Anwendung muss die Daten nur irgendwie verarbeiten und formatieren. Der FB Weg ist klar.

Wenn eine Webanwendung eine POST-Anforderung verarbeitet, möchte der Benutzer die auf der Site gespeicherten Informationen ändern. Richtig, die Informationen werden normalerweise nicht in Anwendungsvariablen gespeichert, sie befinden sich in einer Datenbank oder in einer Flat-Datei, aber trotzdem habe ich das Gefühl, dass ich nicht richtig in der grokking FP bin.

Gibt es ein Muster für die Handhabung von Updates für statische Daten in einer FP-Sprache?

Mein vages Bild davon ist, dass die Anwendung die Anfrage und den dann aktuellen Stand der Site übergeben wird. Die Anwendung erledigt ihre Aufgabe und gibt den neuen Standort zurück. Wenn sich der aktuelle Site-Status seit dem Start der Anwendung nicht geändert hat, wird der neue Status zum aktuellen Status und die Antwort wird zurück an den Browser gesendet (das ist mein dunkles Bild von Clojures Stil); wenn der aktuelle Zustand geändert worden ist (von einem anderen Thread, na ja, passiert etwas anderes ...

+0

IO per Definition ist zwingend notwendig – Cine

+1

Sehen Sie, wenn Sie diesem Syllogismus folgen: (a) I/O ist zwingend erforderlich; (b) jedes nicht-triviale Programm erfordert I/O; daher ist jedes funktionale Programm trivial. Ich sage nicht, dass ich das glaube, ich sage nur, ich verstehe nicht, warum es falsch ist. Das ist die Essenz meiner ursprünglichen Frage. – Malvolio

+0

Funktionelle Programmierung bedeutet, jemand anderen die I/O zu tun. :-D – ephemient

Antwort

-1

Hinweis: Ich weiß nicht, überhaupt Scala, so vermute ich, nur an der Syntax von Beispielen

.

Hier ist eine funktionelle Art und Weise eine Karte der Umsetzung.

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) 

Statt einer traditionellen Datenstruktur kann eine Karte nur eine Funktion sein, um Einträge aus der Karte hinzuzufügen oder zu entfernen, eine Funktion besteht mit dem bestehenden Karte, um eine neue Karte zu erhalten

Ich denke auch gerne an Webapplikationen. Eine Webanwendung konvertiert Anfragen in Transaktionen. Eine Transaktion ändert einen Status in einen anderen, aber sie kann auf den aktuellen Status oder einen früheren Status oder einen unbekannten zukünftigen Status angewendet werden. Transaktionen allein sind nicht sinnvoll; Irgendetwas muss sie sequenzieren und nacheinander anwenden. Aber der Request-Handler muss gar nicht darüber nachdenken.

Betrachten Sie als Beispiel, wie dieFramework-Modelle angeben. Eine eingehende Anfrage wird an einen Handler weitergeleitet, der innerhalb einer Monade läuft. Teilweise dank einiger TH Magie serialisiert das Framework das resultierende mote und fügt es dem Ende des wachsenden Transaktionsprotokolls hinzu. Um den neuesten Status zu bestimmen, kann man einfach durch die Protokolldatei gehen und Transaktionen nacheinander anwenden. (Happstack kann auch "Checkpoints" schreiben, aber sie sind für den Betrieb nicht unbedingt notwendig.)

6

Ein Weg, um mit dieser Art von Problem in einer reinen FP-Umgebung umzugehen, sind Monaden wie in Haskell (zB IO und State), Aber es gibt Alternativen wie "eindeutige Typen" (die nur einen Verweis auf einen Wert zulassen) in Clean.

Es gibt nicht viel zu grok hier: Wenn Sie änderbaren Zustand haben, dann müssen Sie irgendwie Zugriff darauf beschränken, in eine Weise, dass jede Änderung dieses Zustandes ist als eine „neue Version“ wahrgenommen von diese Struktur aus dem Rest des Programms. Z.B. Sie können sich Haskells IO als "Rest der Welt" vorstellen, aber mit einer Art Uhr, die daran befestigt ist. Wenn Sie etwas mit IO machen, tickt die Uhr, und Sie sehen nie wieder dasselbe IO.Das nächste Mal, wenn du es berührst, ist es ein anderes IO, eine andere Welt, in der alles, was du getan hast, bereits passiert ist.

Im wirklichen Leben können Sie "sehen", wie sich die Dinge in einem Film "ändern" - das ist die imperative Ansicht. Aber wenn Sie den Film greifen, sehen Sie nur eine Reihe von kleinen unveränderlichen Bildern, ohne irgendeine Spur von "Veränderung" - das ist die FP-Ansicht. Beide Ansichten sind gültig und "wahr" in ihrem eigenen Kontext.

Jedoch, wenn Sie Scala verwenden, können Sie veränderbaren Zustand haben - kein Problem hier. Scala braucht dafür kein besonderes Handling, und es ist nichts falsch daran, es zu benutzen (obwohl es als "guter Stil" gilt, die "unreinen" Flecken so klein wie möglich zu halten).

+0

Wie übersetze ich diese Monaden in reale I/O, besonders in einer Multithread-Umgebung? (Fühlen Sie sich frei, mir nur einen Link zu geben, wenn Sie einen haben). Ja, ich weiß, Scala erlaubt mir, dem FP-Paradigma zu entkommen, aber ich will nicht raus, ich will weiterkommen. – Malvolio

+0

Ich bin definitiv kein Experte dafür, aber es scheint die vielversprechendste Technik dafür zu sein: http://en.wikipedia.org/wiki/Functional_reactive_programming – Landei

3

Die Antwort ist Monaden. Konkret würden der Staat und die Monaden dies vollständig handhaben.

Hier ist ein Beispiel dafür, wie diese funktionieren würde:

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)) 
} 

Beachten Sie, dass es nicht eine einzige Sache ist, die im Programm wandelbar ist, und es wird jedoch hält die „Datenbank“ zu ändern (dargestellt von einem unveränderlichen Map), bis es nicht mehr genügend Speicher hat. Das IO-Objekt simuliert hypothetische HTTP-PUT-Anforderungen, indem Paare zufällig generierter Schlüssel und Werte zugeführt werden.

Das ist also die allgemeine Struktur eines funktionalen Programms, das HTTP PUT verarbeitet und eine Datenbank einspeist. Stellen Sie sich eine Datenbank als unveränderliches Objekt vor - jedes Mal, wenn Sie sie "aktualisieren", erhalten Sie ein neues Datenbankobjekt.

+0

Aber was passiert, wenn Sie viele Arten von Anfragen und viele Arten von Ressourcen und mehrere Schichten von Abstraktion haben? – IttayD

+0

@IttayD Was sehen Sie als ein Problem? In einer nicht-funktionalen Umgebung haben Sie Funktionen verkettet und verschachtelt, die eine Anfrage in eine Antwort verwandeln. Es könnte auf dem Kopf stehen - der Code ruft etwas auf, um die Antwort zu liefern, anstatt sie zurückzugeben, aber siehe Lift, BlueEyes, Unfiltered, Scalatra, Circumflex usw. Sie funktionieren alle als Funktionen von Anfrage zu Antwort. Hier brauchen wir (Zustand, Anfrage) zu (Zustand, Antwort). Tatsächlich macht BlueEyes das ein bisschen, indem es "Kontext" übergibt. –

3

Die idiomatische FP Antwort auf asynchrone Modifikationen des Zustandes über das Internet ist continuation-passing style, ich denke: die aktuelle Lauffunktion vorgesehen ist eine Fortsetzung Funktion als „next action“, das calling mit Argumenten aus der aktuellen Berechnung ergebenden (das Analogon des Imperativs return) stellt State-Passing dar.

Auf das Web angewendet bedeutet dies, dass wenn der Server eine Eingabe vom Benutzer benötigt, nur die Fortführung der Sitzung gespeichert wird. Wenn der Benutzer mit einigen Informationen antwortet, wird die gespeicherte Fortsetzung wiederhergestellt, die von ihm bereitgestellte Eingabe wird durch eine Berechnung verarbeitet, und sie wird dann als Wert der Fortsetzung zurückgegeben.

Sie können ein Beispiel für fortsetzungsbasierte Webanwendungsframework here finden. Eine detaillierte High-Level-Beschreibung der Idee ist here. Siehe auch die Fülle von Ressourcen und FP-Anwendungen, die diese here implementieren.

Weiterleitungen sind supported in Scala as of 2.8, und es gibt eine 200-300 LoC example of a webserver in Fortsetzung-Weitergabe Stil in der Verteilung.

Verwandte Themen