我如何编写这个更通用的版本`Control.Monad.Writer.censor`?

ntc*_*tc2 11 monads haskell

该标准Control.Monad.Writer.censor是双重的标准 Control.Monad.Reader.local,与censor修改作家状态 计算和local修改读卡器状态 之前的计算:

censor :: (w -> w) -> Writer w a -> Writer w a
local  :: (r -> r) -> Reader r a -> Reader r a
Run Code Online (Sandbox Code Playgroud)

然而,ReaderWritermonad并不完全对称.也就是说,除了编写器状态之外,编写器计算产生结果,并且我正在尝试编写censor利用这种不对称性的替代版本.我想写一个函数

censorWithResult :: (a -> w -> w) -> Writer w a -> Writer w a
Run Code Online (Sandbox Code Playgroud)

除了写入器状态之外a -> w -> w,它还接收接收计算结果的类型的变换器.我不知道怎么写使用此功能,和.telllistenpass

我期望的精确行为censorWithResult是,如果

ma :: Writer w a
f  :: a -> w -> w
Run Code Online (Sandbox Code Playgroud)

runWriter ma = (r , y)
Run Code Online (Sandbox Code Playgroud)

然后

runWriter (censorWithResult f ma) = (r , f r y)
Run Code Online (Sandbox Code Playgroud)

runWriter (censor g ma) = (r , g y)
Run Code Online (Sandbox Code Playgroud)

g :: w -> w.

这对于理解这个问题不是必要的,但这里是激励示例的简化版本:

import Control.Applicative
import Control.Monad.Writer

-- Call-trace data type for functions from 'Int' to 'Int'.
--
-- A 'Call x subs r' is for a call with argument 'x', sub calls
-- 'subs', and result 'r'.
data Trace = Call Int Forest Int
type Forest = [Trace]

-- A writer monad for capturing call traces.
type M a = Writer Forest a

-- Recursive traced negation function.
--
-- E.g. we expect that
--
--   runWriter (t 2) = (-2 , Call 2 [Call 1 [Call 0 [] 0] -1] -2)
t , n :: Int -> M Int
t x = trace n x
n x = if x <= 0 then pure 0 else subtract 1 <$> t (x - 1)

trace :: (Int -> M Int) -> (Int -> M Int)
trace h x = do
  censorWithResult (\r subs -> [Call x subs r]) (h x)

-- The idea is that if 'ma :: Writer w a' and 'runWriter ma = (r , y)'
-- then 'runWriter (censorWithResult f ma) = (r , f r y)'. I.e.,
-- 'censorWithResult' is like 'Control.Monad.Writer.censor', except it
-- has access to the result of the 'Writer' computation, in addition
-- to the written data.
censorWithResult :: (a -> w -> w) -> Writer w a -> Writer w a
censorWithResult = undefined
Run Code Online (Sandbox Code Playgroud)

Dan*_*ner 21

我期望的精确行为censorWithResult是,如果

ma :: Writer w a
f  :: a -> w -> w
Run Code Online (Sandbox Code Playgroud)

runWriter ma = (r , y)
Run Code Online (Sandbox Code Playgroud)

然后

runWriter (censorWithResult f ma) = (r , f r y)
Run Code Online (Sandbox Code Playgroud)

那么,让我们这样做吧.你需要知道的唯一的事情writer就是左反runWriter.然后我们得到以下均衡链,首先通过writer应用于双方,然后通过消除左反.

        runWriter (censorWithResult f ma)  =        (r, f r y)
writer (runWriter (censorWithResult f ma)) = writer (r, f r y)
                   censorWithResult f ma   = writer (r, f r y)
Run Code Online (Sandbox Code Playgroud)

我们现在唯一需要做的就是插入你的等式runWriter ma = (r, y):

censorWithResult f ma = let (r, y) = runWriter ma in writer (r, f r y)
Run Code Online (Sandbox Code Playgroud)

是不是等式推理盛大?


Pet*_*lák 5

如果我们只允许使用tell,pass并且listen,唯一能够访问输出的功能是

-- | `pass m` is an action that executes the action `m`, which returns a value
-- and a function, and returns the value, applying the function to the output. 
pass :: (MonadWriter w m) => m (a, w -> w) -> m a
Run Code Online (Sandbox Code Playgroud)

因此censorWithResult,我们需要部分应用类型的给定函数a -> w -> w来获取w -> w和处理它pass.这可以完成为

censorWithResult :: (MonadWriter w m) => (a -> w -> w) -> m a -> m a
censorWithResult f m = pass $ do
    a <- m
    return (a, f a)
Run Code Online (Sandbox Code Playgroud)

内部的动作pass执行给定的动作,部分适用f于它,pass然后相应地修改输出.