2010-11-18 13 views
5

Tengo un estado general que es esencialmente un 3-tuple, y una serie de funciones que se refieren a partes de ese estado. Estoy tratando de encontrar un conjunto de adaptadores genéricos para tales funciones, de modo que pueda usarlos en una canalización de mónadas de estado.Mónada de estado: ¿funciones de adaptación que solo funcionan con partes del estado?

Esto es posiblemente totalmente erróneo; siéntete libre de hacer ese caso.

Pido disculpas de antemano por la mezcla de Java y pidgin Scala. De hecho, estoy haciendo esto en Java como un ejercicio de aprendizaje, pero nadie tiene tiempo para leer todo eso. He eludido una gran cantidad de complejidad desinteresada por el bien de la discusión; no te preocupes por el modelado del dominio.

El estado en cuestión es la siguiente:

ImportState(row:CsvRow, contact:Contact, result:ImportResult) 

ImportResult es uno de ADD, MERGE o REJECT.

Las funciones que hemos definido son los siguientes:

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 

Los adaptadores he definido hasta ahora son bastante simples, y se ocupan de las propiedades individuales de 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 

El (roto) de tuberías es como la siguiente (en 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); 

El immediat El problema es que merge devuelve una tupla (Contact, ImportResult), que me gustaría aplicar a dos propiedades del estado (contact y result), manteniendo la tercera propiedad, row.

Hasta ahora, yo he llegado con un par de enfoques para la adaptación de fusión que tanto chupar:

  1. definir algunas funciones que empacar y desempacar tuplas, y utilizarlos directamente en la tubería. Esta opción es extremadamente ruidosa.

  2. Defina un adaptador único para ImportState y merge. Esta opción se siente como darse por vencido.

¿Hay una manera mejor?

Respuesta

7

Tu pregunta está etiquetada Haskell - Espero que eso signifique que puedes leer Haskell, y no que alguien haya visto 'mónadas' y lo haya agregado. En ese supuesto, voy a estar hablando Haskell en esta respuesta, ya que es el lenguaje que creo en estos días;)

Hay un concepto útil llamado "lentes funcionales" con un par de implementaciones de la biblioteca Haskell.La idea central es que una "lente" es un par de funciones:

data Lens a b = Lens { extract :: (a -> b), update :: (a -> b -> a) } 

Esto representa una forma funcional de conseguir y "partes" de actualización de una estructura. Con un tipo como éste, puede escribir una función como:

subState :: Lens a b -> State a t -> State b t 
subState lens st = do 
    outer <- get 
    let (inner, result) = runState st (extract lens outer) 
    put (update lens outer inner) 
    return result 

traducirlo en Java suena como un ejercicio interesante (y posiblemente todo un reto)!

+0

Gracias! Lo etiqueté Haskell porque puedo leer más o menos Haskell, con la ayuda de un diccionario Haskell-Inglés. – loganj

+0

Esto parece que une mis funciones get/set. Para un estado con n campos, ¿existe un modismo Haskell para componer funciones de extracción y actualización para que no necesite n^2-1 de cada una? – loganj

+0

@loganj: sí, en el paquete fclabels, por ejemplo, el tipo de lente es una instancia de "Data.Category.Category", lo que significa que define un "lente de identidad" 'id :: Lens aa' y una" composición de lente " '(.) :: Lente bc -> Lente ab -> Lente ac'. La definición es bastante simple: el nuevo get es solo la composición de los antiguos, y con una función auxiliar 'modify :: Lens ab -> (b -> b) -> a -> a'., The new 'set 'es algo así como: 'modificar lens1 outer (\ inner -> update lens2 inner x)'. Esto es un poco feo debido a mi elección de orden de argumento en 'update', pero espero que tenga sentido de todos modos. – mokus

0

Observe cómo reemplazar el enfoque de tupla con clases de casos. Obtienes mucho gratis en una estructura que es prácticamente tan fácil de definir, en particular un método de copia generado por el compilador que te permite crear una copia de una instancia, cambiando solo los campos que deseas cambiar.

2

interesante que escribió esta operación exacta anoche usando fclabels:

withGame :: (r :-> r', s :-> s') -> GameMonad r' s' a -> GameMonad r s a 
withGame (l1,l2) act = do 
    (r,s) <- (,) <$> askM l1 <*> getM l2 
    (a, s') <- liftIO $ runGame r s act 
    setM l2 s' 
    return a 

GameMonad es un nuevo tipo que es una pila transformador mónada del estado, lector, IO. También estoy usando un poco de código de estilo de functor aplicativo, no dejes que te desanime, es más o menos lo mismo que mokus.

Cuestiones relacionadas