有没有更可读的方法来重写这个纯函数来使用Writer Monad?

Ret*_*Fun 5 monads haskell

我使用列表推导编写了一个递归算法来执行递归.我认为我的代码清晰易读,其产生的结果是正确的.

但是,我发现很难理解我的代码在某些输入上的性能.我认为使用Writer monad将一些日志记录放入我的代码中会很有用.

我发现将非monadic代码转换为monadic非常困难.最终我得到它编译并正确运行,但monadic代码比原始代码更难理解.

原始问题太复杂了,无法在这里解释,所以我写了一个玩具示例,显示非monadic和monadic方法,但实际上并没有计算任何有用的东西!

所以我的问题是:有没有更好的方法来编写函数fMonadic,这样它更具可读性?

import Control.Monad (forM)
import Control.Monad.Writer (Writer, runWriter, tell)

fun :: Int -> [[Int]] -> [[Int]]
fun a b = map (map (a +)) b

fNonMonadic :: [[Int]] -> [[Int]]
fNonMonadic [] = [[]]
fNonMonadic (first : rest) =
    [ first ++ s
    | e <- first
    , s <- fNonMonadic $ fun e rest]

fMonadic :: [[Int]] -> Writer [String] [[Int]]
fMonadic [] = do
    tell ["base case"]
    return [[]]
fMonadic (first : rest) =
    fmap concat . forM first $ \ e -> do
        tell ["recursive case " ++ show e]
        fmap (map (first ++)) $ fMonadic $ fun e rest

main = do
    let arg = [[0, 1], [20, 30], [400, 500]]
    print $ fNonMonadic arg
    let (a, b) = runWriter $ fMonadic arg
    print a
    mapM_ putStrLn b
Run Code Online (Sandbox Code Playgroud)

lef*_*out 6

装备纯Haskell函数通常很尴尬,这些函数以代数,高度分支的树方式构造,具有日志特征,例如日志记录,这需要更"必要"的结构.然而,有时使用monadic组合器编写甚至纯粹的计算实际上是很自然的,而你的实际上是其中之一.也就是说,核心的列表理解fNonMonadic已基本上使用了列表monad ; 它可以写成:

type ListM = []   -- Just to distinguish where I use list as a monad

fNonMonadic :: [[Int]] -> ListM [Int]
fNonMonadic [] = return []
fNonMonadic (first : rest) = do
    e <- first
    s <- fNonMonadic $ fun e rest
    return $ first ++ s
Run Code Online (Sandbox Code Playgroud)

从那开始,通过使用它作为monad变换器堆栈的基础,添加写入器功能要容易得多.然后必须在变换器形状中使用该列表:

import Control.Monad.Trans.List

fMonTrafo :: [[Int]] -> ListT (Writer [String]) [Int]
fMonTrafo [] = do
    lift $ tell ["base case"]
    return []
fMonTrafo (first : rest) = do
    e <- ListT $ pure first
    lift $ tell ["recursive case " ++ show e]
    s <- fMonTrafo $ fun e rest
    return $ first ++ s
Run Code Online (Sandbox Code Playgroud)

您可能会注意到文档ListT警告基本monad应该是可交换的,Writer实际上不是 - 日志条目的顺序可能会搞砸.我不知道这是否重要.如果是,请查看Daniel Wagner建议的替代实施方案.