I looked at several alternatives to Control.Monad.Trans.List and soon installed the ListT module from the Volkov list-t package.
This gives the same result as my ugly fMonadic function, but more readable code. It also works correctly and leads to readable code in a real problem that I want to solve.
In a real task, ListT-based code is a bit slower than ugly code, but the difference is not enough to make a difference.
Thanks again for your help in this matter.
For reference, here is a revised version of a toy example that performs calculations in three different ways and shows that the answer is the same:
import Control.Monad (forM) import ListT (ListT, fromFoldable, toList) import Control.Monad.Writer (Writer, lift, runWriter, tell) fun :: Int -> [[Int]] -> [[Int]] fun ab = 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 ""
source share