我使用列表推导编写了一个递归算法来执行递归.我认为我的代码清晰易读,其产生的结果是正确的.
但是,我发现很难理解我的代码在某些输入上的性能.我认为使用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)
装备纯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建议的替代实施方案.