Haskell—跟踪记录(初始)状态的更好方法

Haskell—跟踪记录(初始)状态的更好方法,haskell,record,haskell-lens,Haskell,Record,Haskell Lens,我正在处理一些函数,这些函数接受一条记录并返回一条稍微修改过的记录 比如说 import Control.Lens ((%~), (^.), (&)) modifyRecord :: SomeRecord -> SomeRecord -> SomeRecord modifyRecord baseR currentR = currentR & thisPart %~ (fmap (someFn someValue)) where someValue =

我正在处理一些函数,这些函数接受一条记录并返回一条稍微修改过的记录

比如说

import Control.Lens ((%~), (^.), (&))

modifyRecord :: SomeRecord -> SomeRecord -> SomeRecord
modifyRecord baseR currentR = currentR & thisPart %~ (fmap (someFn someValue))
        where someValue = baseR ^. thisPart
函数modifyRecord接受两个参数,都是相同类型的参数

currentR是记录的当前状态

baseR是记录的基本状态

i、 e.未应用任何功能,从未更改

组合这种类型的几个函数意味着我必须组合部分函数,列出它们

[fn1 baseState , fn2 baseState , fn3 baseState ... fnn baseState]
然后我会用foldl flip这样的函数来折叠currentState$

因此,每个fnn baseState本身就是一个类型为 SomeRecord->SomeRecord

我要做的是编写这些函数,使它们只获取记录的当前状态,并自行计算基态

所以

没有实际修改记录本身

我想避免这样做

data SomeRecord = SomeRecord { value1 :: Float
                             , value1Base :: Float
                             , value2 :: Float
                             , value2Base :: Float
                             ...
                             ...
                             , valueN :: Float
                             , valueNBase :: Float
                             }
如果记录本身包含基值,则应用于其上的函数将避免与*基项交互


那可能吗

广义地说,不,这是不可能的:函数必须显式声明其所有输入。也许最干净的方法是使用组合功能。您需要翻转它们的参数,以便未修改的记录排在最后,而不是第一位;一旦你这样做了,你就会

concatM [f1, f2, f3, f4] :: SomeRecord -> SomeRecord -> SomeRecord
根据需要。仅结合两个这样的功能,就有

(>=>) ::
    (SomeRecord -> SomeRecord -> SomeRecord) ->
    (SomeRecord -> SomeRecord -> SomeRecord) ->
    (SomeRecord -> SomeRecord -> SomeRecord)

);f>=>g将首先执行f的修改,然后执行g的修改。如果您更喜欢另一个顺序,为了更接近的行为,还有一个广义上说,不,这是不可能的:函数必须显式声明其所有输入。也许最干净的方法是使用组合功能。您需要翻转它们的参数,以便未修改的记录排在最后,而不是第一位;一旦你这样做了,你就会

concatM [f1, f2, f3, f4] :: SomeRecord -> SomeRecord -> SomeRecord
根据需要。仅结合两个这样的功能,就有

(>=>) ::
    (SomeRecord -> SomeRecord -> SomeRecord) ->
    (SomeRecord -> SomeRecord -> SomeRecord) ->
    (SomeRecord -> SomeRecord -> SomeRecord)
);f>=>g将首先执行f的修改,然后执行g的修改。如果你更喜欢另一个顺序,为了更接近的行为,还有一个听起来像是读者单子的工作

foldl>=>return[fn1,fn2,…fnn]将Kleisli箭头列表减少为一个箭头,与foldl非常相似。id将普通函数列表组合成单个函数

将foldl的结果应用于currentR会生成一个Reader SomeRecord SomeRecord值,该值只需要一个基本记录就可以启动对原始当前记录的修改链并生成最终结果

步骤1和2概括了固定长度的链,如返回电流r>>=fn1>>=fn2>>=fn3

runReader通过从Reader值中提取函数并将其应用于baseR来提供基本记录

听起来像是读者monad的工作

foldl>=>return[fn1,fn2,…fnn]将Kleisli箭头列表减少为一个箭头,与foldl非常相似。id将普通函数列表组合成单个函数

将foldl的结果应用于currentR会生成一个Reader SomeRecord SomeRecord值,该值只需要一个基本记录就可以启动对原始当前记录的修改链并生成最终结果

步骤1和2概括了固定长度的链,如返回电流r>>=fn1>>=fn2>>=fn3

runReader通过从Reader值中提取函数并将其应用于baseR来提供基本记录


将初始状态和当前状态放在元组中,并使用fmap提升只关心当前状态的函数:

ghci> :set -XTypeApplications
ghci> fmap @((,) SomeRecord) :: (a -> b) -> (SomeRecord, a) -> (SomeRecord, b)
但是如果我们有两个函数,形式是SomeRecord,SomeRecord->SomeRecord,我们需要组合它们,会怎么样?我们可以很容易地定义运算符,但它是否已经存在于某个地方

碰巧,类型e有一个实例。这是一个非常简单的comonad,在我们的例子中,它将值与一些环境进行配对,即我们希望携带的原始值

co-kleisli复合运算符可用于链接两个SomeRecord、SomeRecord->SomeRecord函数,以及将它们应用于初始成对值

 ghci> import Control.Comonad
 ghci> (1,7) =>> ((\(o,c) -> c * 2) =>= (\(o,c) -> o + c))
 (1,15)
或者我们可以一直使用=>>:

 ghci> (1,7) =>> (\(o,c) -> c * 2) =>> (\(o,c) -> o + c)
 (1,15)
使用翻转的fmap操作符,我们甚至可以编写一个管道,如

 ghci> (1,2) =>> (\(x,y) -> y+2) <&> succ =>> (\(x,y) -> x + y)
 (1,6)  

我们还可以使用获取当前值,这可能比snd更好地显示意图

将初始状态和当前状态放在一个元组中,并使用fmap提升只关心当前状态的函数:

ghci> :set -XTypeApplications
ghci> fmap @((,) SomeRecord) :: (a -> b) -> (SomeRecord, a) -> (SomeRecord, b)
但是如果我们有两个函数,形式是SomeRecord,SomeRecord->SomeRecord,我们需要组合它们,会怎么样?我们可以很容易地定义运算符,但它是否已经存在于某个地方

碰巧,类型e有一个实例。这是一个非常简单的comonad,在我们的例子中,它将值与一些环境进行配对,即我们希望携带的原始值

co-kleisli复合运算符可用于链接两个SomeRecord ,SomeRecord->SomeRecord函数,以及将它们应用于初始成对值的步骤

 ghci> import Control.Comonad
 ghci> (1,7) =>> ((\(o,c) -> c * 2) =>= (\(o,c) -> o + c))
 (1,15)
或者我们可以一直使用=>>:

 ghci> (1,7) =>> (\(o,c) -> c * 2) =>> (\(o,c) -> o + c)
 (1,15)
使用翻转的fmap操作符,我们甚至可以编写一个管道,如

 ghci> (1,2) =>> (\(x,y) -> y+2) <&> succ =>> (\(x,y) -> x + y)
 (1,6)  

我们还可以使用获取当前值,这可能比snd更好地显示意图

你不能将基本记录设置为全局吗?你不能将基本记录设置为全局吗?对于合成来说?@DanielWagner Control.Comonad.=>=也许你应该将其添加到答案正文中!有人可能会注意到pairs的Comonad实例和functions的Monad实例之间的相似性,这是由于curried函数和uncarried函数之间的同构造成的。@chepner在这种情况下,我更喜欢comonadic解决方案,因为它强调了一个静态值与原始附加值一起出现。对于合成?@DanielWagner Control.Comonad.=>=也许您应该将其添加到答案正文本身!有人可能会注意到pairs的Comonad实例和functions的Monad实例之间的相似性,这是由于curried函数和uncarried函数之间的同构造成的。@chepner在本例中,我更喜欢comonadic解决方案,因为它强调静态值与原始附加值一起出现。