州monad - 适应仅与州的部分地区合作的功能?

时间:2010-11-18 14:35:19

标签: scala haskell functional-programming state monads

我有一个基本上是3元组的一般状态,以及许多函数,每个函数都涉及该状态的一部分。我正在尝试为这些函数设计一组通用适配器,以便我可以在State monad管道中使用它们。

这可能完全是错误的;随意做出这种情况。

我提前为Java和pidgin Scala的混合道歉。我实际上是用Java作为学习练习,但是没有人有时间阅读这些内容。为了讨论,我已经省略了许多无趣的复杂性;不要担心域建模。

有问题的国家是:

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

ImportResultADDMERGEREJECT中的一个。

我定义的功能是:

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),我想将其应用于州的两个属性(contactresult),而保留第三个属性row

到目前为止,我已经提出了两种适应合并的方法,这两种方法都很糟糕:

  1. 定义一些打包和解包元组的函数,并直接在管道中使用它们。这个选项非常嘈杂。

  2. ImportStatemerge定义一次性适配器。这个选项感觉就像放弃了。

  3. 有更好的方法吗?

3 个答案:

答案 0 :(得分:7)

你的问题被标记为Haskell - 我希望这意味着你可以阅读Haskell,而不是有人看到'monads'并添加它。根据这个假设,我将在这个答案中讲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听起来像一个有趣的(可能非常具有挑战性的)练习!

答案 1 :(得分:2)

有趣的是我昨晚使用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的monad变换器堆栈。我也使用了一些应用程序样式代码,不要让它让你失望,它与mokus几乎相同。

答案 2 :(得分:0)

看看用case类替换元组方法。您可以在一个几乎易于定义的结构中免费获得很多,特别是编译器生成的复制方法,它允许您创建实例的副本,只更改您想要更改的字段。