Home > OS >  How to convert a haskell List into a monadic function that uses list values for operations?
How to convert a haskell List into a monadic function that uses list values for operations?

Time:12-26

I am having trouble wrapping my head around making to work a conversion of a list into a monadic function that uses values of the list.

For example, I have a list [("dir1/content1", "1"), ("dir1/content11", "11"), ("dir2/content2", "2"), ("dir2/content21", "21")] that I want to be converted into a monadic function that is mapped to a following do statement:

do
   mkBlob ("dir1/content1", "1")
   mkBlob ("dir1/content11", "11")
   mkBlob ("dir2/content2", "2")
   mkBlob ("dir2/content21", "21")
   

I imagine it to be a function similar to this:

contentToTree [] = return 
contentToTree (x:xs) = (mkBlob x) =<< (contentToTree xs)

But this does not work, failing with an error:

• Couldn't match expected type ‘() -> TreeT LgRepo m ()’
              with actual type ‘TreeT LgRepo m ()’
• Possible cause: ‘(>>=)’ is applied to too many arguments
  In the expression: (mkBlob x) >>= (contentToTree xs)
  In an equation for ‘contentToTree’:
      contentToTree (x : xs) = (mkBlob x) >>= (contentToTree xs)
• Relevant bindings include
    contentToTree :: [(TreeFilePath, String)] -> () -> TreeT LgRepo m ()

I do not quite understand how to make it work.

Here is my relevant code:

import Data.Either
import Git
import Data.Map
import Conduit
import qualified Data.List as L
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BL
import Control.Monad (join)

type FileName = String 
    
data Content = Content {
  content :: Either (Map FileName Content) String
} deriving (Eq, Show) 

contentToPaths :: String -> Content -> [(TreeFilePath, String)]
contentToPaths path (Content content) = case content of
  Left m -> join $ L.map (\(k, v) -> (contentToPaths (if L.null path then k else path    "/"    k) v)) $ Data.Map.toList m
  Right c -> [(BS.pack path, c)]

mkBlob :: MonadGit r m => (TreeFilePath, String) -> TreeT r m ()
mkBlob (path, content) = putBlob path
    =<< lift (createBlob $ BlobStream $
              sourceLazy $ BL.fromChunks [BS.pack content])

sampleContent = Content $ Left $ fromList [
  ("dir1", Content $ Left $ fromList [
    ("content1", Content $ Right "1"),
    ("content11", Content $ Right "11")
    ]),
  ("dir2", Content $ Left $ fromList [
    ("content2", Content $ Right "2"),
    ("content21", Content $ Right "21")
    ])
  ]

Would be grateful for any tips or help.

CodePudding user response:

You have:

  • A list of values of some type a (in this case a ~ (String, String)). So, xs :: [a]
  • A function f from a to some type b in a monadic context, m b. Since you're ignoring the return value, we can imagine b ~ (). So, f :: Monad m => a -> m ().

You want to perform the operation, yielding some monadic context and an unimportant value, m (). So overall, we want some function doStuffWithList :: Monad m => [a] -> (a -> m ()) -> m (). We can search Hoogle for this type, and it yields some results. Unfortunately, as we've chosen to order the arguments, the first several results are little-used functions from other packages. If you scroll further, you start to find stuff in base - very promising. As it turns out, the function you are looking for is traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f (). With that, we can replace your do-block with just:

traverse_ mkBlob [ ("dir1/content1", "1")
                 , ("dir1/content11", "11")
                 , ("dir2/content2", "2")
                 , ("dir2/content21", "21")
                 ]

As it happens there are many names for this function, some for historical reasons and some for stylistic reasons. mapM_, forM_, and for_ are all the same and all in base, so you could use any of these. But the M_ versions are out of favor these days because really you only need Applicative, not Monad; and the for versions take their arguments in an order that's convenient for lambdas but inconvenient for named functions. So, traverse_ is the one I'd suggest.

CodePudding user response:

Assuming mkBlob is a function that looks like

mkBlob :: (String, String) -> M ()

where M is some specific monad, then you have the list

xs = [("dir1/content1", "1"), ("dir1/content11", "11"), ("dir2/content2", "2"), ("dir2/content21", "21")]

whose type is xs :: [(String, String)]. The first thing we need is to run the mkBlob function on each element, i.e. via map.

map mkBlob xs :: [M ()]

Now, we have a list of monadic actions, so we can use sequence to run them in sequence.

sequence (map mkBlob xs) :: M [()]

The resulting [()] value is all but useless, so we can use void to get rid of it

void . sequence . map mkBlob $ xs :: M ()

Now, void . sequence is called sequence_ in Haskell (since this pattern is fairly common), and sequence . map is called mapM. Putting the two together, the function you want is called mapM_.

mapM_ mkBlob xs :: M ()
  • Related