我有一个基本上是3元组的一般状态,以及许多函数,每个函数都涉及该状态的一部分。我正在尝试为这些函数设计一组通用适配器,以便我可以在State monad管道中使用它们。
这可能完全是错误的;随意做出这种情况。
我提前为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
。
到目前为止,我已经提出了两种适应合并的方法,这两种方法都很糟糕:
定义一些打包和解包元组的函数,并直接在管道中使用它们。这个选项非常嘈杂。
为ImportState
和merge
定义一次性适配器。这个选项感觉就像放弃了。
有更好的方法吗?
答案 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类替换元组方法。您可以在一个几乎易于定义的结构中免费获得很多,特别是编译器生成的复制方法,它允许您创建实例的副本,只更改您想要更改的字段。