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:
Define some functions that pack and unpack tuples, and use them directly in the pipeline. This option is extremely noisy.
Define a one-off adapter for ImportState
and merge
. This option feels like giving up.
Is there a better way?
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With