Ich habe einen allgemeinen Zustand, der im Wesentlichen ein 3-Tupel ist, und eine Reihe von Funktionen, die sich jeweils mit Teilen dieses Zustands befassen. Ich versuche, eine Reihe von generischen Adaptern für solche Funktionen auszuarbeiten, so dass ich sie in einer State-Monad-Pipeline verwenden kann.State monad - Funktionen anpassen, die nur mit Teilen des Staates funktionieren?
Dies ist möglicherweise völlig falsch; fühlen Sie sich frei, diesen Fall zu machen.
Ich entschuldige mich im Voraus für Mix von Java und Pidgin Scala. Ich mache das tatsächlich in Java als Lernübung, aber niemand hat Zeit, all das zu lesen. Ich habe eine Menge uninteressanter Komplexität zugunsten der Diskussion weggelassen; Mach dir keine Sorgen über die Domänenmodellierung. dies ist
Der Staat in Frage:
ImportState(row:CsvRow, contact:Contact, result:ImportResult)
ImportResult
ein von ADD
, MERGE
oder REJECT
.
Die Funktionen I definiert haben, sind diese:
def rowToContact: ImportRow => Contact
def findMergeCandidates: Contact => (Contact, List[Contact])
// merges, or declines to merge, setting the result
def merge: (Contact, List[Contact]) => (Contact, ImportResult)
def persist: Contact => ImportResult
def commitOrRollback: ImportState => ImportState
def notifyListener: ImportState => Nothing
Die Adapter ich bisher definiert haben, sind ziemlich einfach, und befassen sich mit individuellen Eigenschaften von ImportState
:
def getRow: ImportState => ImportRow
def getContact: ImportState => Contact
def setRow(f: _ => ImportRow): ImportState => ImportState
def setContact(f: _ => Contact): ImportState => ImportState
def setResult(f: _ => ImportResult): ImportState => ImportState
Die (gebrochene) sieht in etwa so aus (in Java):
State.<ImportState>init()
.map(setRow(constant(row)))
.map(setContact(getRow.andThen(rowToContact)))
.map(getContact.andThen(findMergeCandidates).andThen(merge)) // this is where it falls apart
.map(setResult(getContact.andThen(persist)))
// ... lots of further processing of the persisted contact
.map(commitOrRollback)
.map(notifyListener);
Das sofort Das Problem ist, dass merge
ein Tupel (Contact, ImportResult)
zurückgibt, das ich auf zwei Eigenschaften des Staates (contact
und result
) anwenden möchte, während die dritte Eigenschaft beibehalten wird, row
.
Bisher habe ich mit ein paar Ansätze zur Anpassung der Zusammenführung kommen, die beide saugen:
einige Funktionen definieren, die Tupel packen und entpacken, und sie direkt in der Pipeline verwenden. Diese Option ist extrem laut.
Definieren Sie einen einmaligen Adapter für
ImportState
undmerge
. Diese Option fühlt sich an wie aufgeben.
Gibt es einen besseren Weg?
Danke! Ich habe es mit Haskell getaggt, weil ich Haskell mehr oder weniger lesen kann, mit Hilfe eines Haskell-Wörterbuchs. – loganj
Das sieht so aus, als ob es meine Get/Set-Funktionen paaren würde. Gibt es für einen Zustand mit n Feldern ein Haskell-Idiom zum Verfassen von Extrakt- und Aktualisierungsfunktionen, so dass Sie nicht n^2-1 von jedem benötigen? – loganj
@loganj: ja, im fclabels-Paket zum Beispiel ist der Linsentyp eine Instanz von "Data.Category.Category", was bedeutet, dass er ein "identity lens" definiert "id :: Lens aa" und eine "lens composition" '(.) :: Objektiv bc -> Objektiv ab -> Objektiv ac'. Die Definition ist ziemlich einfach - das neue get ist nur die Zusammensetzung der alten, und mit einer Hilfsfunktion 'modifizieren :: Lens ab -> (b -> b) -> a -> a '., Das neue' set 'ist etwas wie: 'modify lens1 outer (\ inner -> update lens2 inner x)'. Das ist ein bisschen hässlich aufgrund meiner Wahl der Argumentreihenfolge in 'update', aber hoffentlich macht es trotzdem Sinn. – mokus