我使用列表推导编写了一个递归算法来执行递归。我认为我的代码清晰易读,所产生的结果是正确的。
但是,我发现很难理解我的代码在某些输入上的性能。我认为使用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
答案 0 :(得分:6)
装备纯Haskell函数通常很尴尬,这些函数以代数,高度分支的树方式构造,具有日志特征,例如日志记录,这需要更“必要”的结构。然而,有时使用monadic组合器编写甚至纯粹的计算实际上是很自然的,而你的实际上是其中之一。也就是说,fNonMonadic
核心的列表理解已基本上使用 list 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
从此开始,通过将编写器功能用作 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
您可能会注意到ListT
的文档警告基本monad应该是可交换的,Writer
实际上不是 - 日志条目的顺序可能会混乱起来。我不知道这是否重要。如果是,请查看the alternative implementation suggested by Daniel Wagner。
答案 1 :(得分:2)
我查看了Control.Monad.Trans.List的几个替代方案,很快就从Volkov的list-t包中找到了模块ListT。
这给出了与我丑陋的fMonadic函数相同的结果,但代码更易读。它也可以正常工作,并导致可读的代码,在我想解决的实际问题中。
在真正的问题中,基于ListT的代码比丑陋的代码运行得稍慢,但差别不足以解决问题。
再次感谢leftaroundabout对此的帮助。
作为参考,这里是玩具示例的修订版本,以三种不同的方式进行计算并显示答案是相同的:
import Control.Monad (forM)
import ListT (ListT, fromFoldable, toList)
import Control.Monad.Writer (Writer, lift, runWriter, tell)
fun :: Int -> [[Int]] -> [[Int]]
fun a b = map (map (a +)) b
fNonMonadic :: [[Int]] -> [[Int]]
fNonMonadic [] = [[]]
fNonMonadic (first : rest) = do
e <- first
s <- fNonMonadic $ fun e rest
return $ first ++ s
-- The above do notation means the same as this list comprehension:
-- [ 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
fMonTrafo :: [[Int]] -> ListT (Writer [String]) [Int]
fMonTrafo [] = do
lift $ tell ["base case"]
return []
fMonTrafo (first : rest) = do
e <- fromFoldable first
lift $ tell ["recursive case " ++ show e]
s <- fMonTrafo $ fun e rest
return $ first ++ s
main = do
let arg = [[0, 1], [20, 30], [400, 500]]
let x = fNonMonadic arg
print x
let (a, b) = runWriter $ fMonadic arg
print a
mapM_ putStrLn b
let (c, d) = runWriter $ toList $ fMonTrafo arg
print c
mapM_ putStrLn d
putStrLn $ if x == a then "fNonMonadic == fMonadic" else error ""
putStrLn $ if x == c then "fNonMonadic == fMonTrafo" else error ""
putStrLn $ if b == d then "fMonadic log == fMonTrafo log" else error ""