Государственная монада - адаптировать функции, которые работают только с частями государства? - PullRequest
5 голосов
/ 18 ноября 2010

У меня есть общее состояние, которое по сути состоит из трех кортежей, и несколько функций, каждая из которых связана с частями этого состояния. Я пытаюсь разработать набор универсальных адаптеров для таких функций, чтобы я мог использовать их в конвейере монады состояний.

Возможно, это совершенно неправильно; не стесняйтесь делать это дело.

Заранее прошу прощения за смесь Java и pidgin Scala. На самом деле я делаю это на Java в качестве учебного упражнения, но никто не успевает все это прочитать. Я исключил много неинтересных сложностей ради обсуждения; не беспокойтесь о моделировании предметной области.

Состояние, о котором идет речь, таково:

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

ImportResult является одним из ADD, MERGE или REJECT.

Я определил следующие функции:

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

Адаптеры, которые я определил, довольно просты и имеют индивидуальные свойства 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

(Неработающий) конвейер выглядит примерно так (в 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);

Непосредственная проблема заключается в том, что merge возвращает кортеж (Contact, ImportResult), который я хотел бы применить к двум свойствам состояния (contact и result), сохраняя при этом третье свойство row .

До сих пор я придумал пару подходов к адаптации слияния, которые оба отстой:

  1. Определите некоторые функции, которые упаковывают и распаковывают кортежи, и используйте их непосредственно в конвейере. Эта опция очень шумная.

  2. Определите одноразовый адаптер для ImportState и merge. Эта опция кажется сдачей.

Есть ли лучший способ?

Ответы [ 3 ]

7 голосов
/ 18 ноября 2010

Ваш вопрос помечен Haskell - я надеюсь, это означает, что вы можете читать Haskell, а не то, что кто-то видел «монады» и добавил его. Исходя из этого, я буду говорить на Haskell в этом ответе, так как это язык, который я думаю в эти дни;)

Существует полезная концепция, называемая "функциональные линзы", с парой реализаций библиотеки Haskell. Основная идея заключается в том, что «объектив» - это пара функций:

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

Это функциональный способ получения и обновления «частей» структуры. С таким типом вы можете написать такую ​​функцию, как:

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

Перевод этого на Java звучит как интересное (и, возможно, довольно сложное) упражнение!

2 голосов
/ 18 ноября 2010

Интересно, я написал эту точную операцию вчера вечером, используя 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 - это новый тип, представляющий собой стек монадных преобразователей состояний, считыватель, IO.Я также использую немного аппликативного кода в стиле функтора, не позволяя ему оторваться, это почти то же самое, что и mokus.

0 голосов
/ 18 ноября 2010

Взгляните на замену подхода кортежей на классы case.Вы получаете много бесплатно в структуре, которую практически так же легко определить, в частности, метод копирования, сгенерированный компилятором, который позволяет вам создавать копию экземпляра, изменяя только те поля, которые вы хотите изменить.

...