Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

State monad - adapt functions that only work with parts of the state?

I have a general state which is essentially a 3-tuple, and a number of functions which each concern themselves with parts of that state. I'm trying to work out a set of generic adapters for such functions, so that I can use them in a State monad pipeline.

This is possibly entirely wrongheaded; feel free to make that case.

I apologize in advance for mix of Java and pidgin Scala. I'm actually doing this in Java as a learning exercise, but nobody has time to read all that. I've elided a lot of uninteresting complexity for the sake of discussion; don't worry about the domain modeling.

The state in question is this:

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

ImportResult is one of ADD, MERGE, or REJECT.

The functions I've defined are these:

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

The adapters I've defined so far are pretty simple, and deal with individual properties of 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

The (broken) pipeline looks something like this (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);

The immediate problem is that merge returns a tuple (Contact, ImportResult), which I'd like to apply to two properties of the state (contact and result), while keeping the third property, row.

So far, I've come up with a couple of approaches to adaptation of merge that both suck:

  1. Define some functions that pack and unpack tuples, and use them directly in the pipeline. This option is extremely noisy.

  2. Define a one-off adapter for ImportState and merge. This option feels like giving up.

Is there a better way?

like image 770
loganj Avatar asked Nov 18 '10 14:11

loganj


2 Answers

Your question is tagged Haskell - I'm hoping that means you can read Haskell, and not that someone saw 'monads' and added it. On that assumption, I'll be speaking Haskell in this answer, since it's the language I think in these days ;)

There's a useful concept called "functional lenses" with a couple Haskell library implementations. The core idea is that a "lens" is a pair of functions:

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

This represents a functional way of getting and updating "parts" of a structure. With a type like this, you can write a function such as:

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

Translating that into Java sounds like an interesting (and possibly quite challenging) exercise!

like image 171
mokus Avatar answered Sep 20 '22 20:09

mokus


Interesting I wrote this exact operation last night using 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 is a new type that is a monad transformer stack of state, reader, IO. I'm also using a bit of applicative functor style code don't let it put you off, it's pretty much the same as mokus.

like image 43
snk_kid Avatar answered Sep 20 '22 20:09

snk_kid